From 313eff1c952b39544e79d843d06572b00972d672 Mon Sep 17 00:00:00 2001 From: Sam Keen Date: Mon, 8 May 2023 13:41:55 +0100 Subject: [PATCH] feat(governance): proposal details page improvements (#3611) --- .../src/integration/view/proposal.cy.ts | 4 +- .../connect-to-vega/connect-to-vega.tsx | 1 + .../src/components/heading/heading.tsx | 2 +- .../governance/src/i18n/translations/dev.json | 14 +- .../current-proposal-state.tsx | 76 +++- .../proposal-change-table.spec.tsx | 16 - .../proposal-change-table.tsx | 19 +- .../proposal-header.spec.tsx | 403 ++++++++++-------- .../proposal-header.tsx | 124 +++--- .../components/proposal-info-label/index.tsx | 1 + .../proposal-info-label.tsx | 35 ++ .../proposal-terms-json.tsx | 24 +- .../proposal-votes-table.spec.tsx | 10 +- .../proposal-votes-table.tsx | 203 +++++---- .../components/proposal/proposal.tsx | 22 +- .../proposals-list-item-details.spec.tsx | 23 +- .../proposals-list-item-details.tsx | 67 +-- .../proposals-list/proposals-list.tsx | 2 +- .../protocol-upgrade-proposals-list-item.tsx | 62 ++- .../components/vote-details/vote-buttons.tsx | 20 +- .../components/vote-details/vote-details.tsx | 28 +- libs/ui-toolkit/src/utils/intent.ts | 2 +- libs/utils/src/lib/format/date.ts | 25 +- 23 files changed, 671 insertions(+), 512 deletions(-) create mode 100644 apps/governance/src/routes/proposals/components/proposal-info-label/index.tsx create mode 100644 apps/governance/src/routes/proposals/components/proposal-info-label/proposal-info-label.tsx diff --git a/apps/governance-e2e/src/integration/view/proposal.cy.ts b/apps/governance-e2e/src/integration/view/proposal.cy.ts index bc78e151a..c64862daf 100644 --- a/apps/governance-e2e/src/integration/view/proposal.cy.ts +++ b/apps/governance-e2e/src/integration/view/proposal.cy.ts @@ -101,11 +101,11 @@ context( ); cy.getByTestId('protocol-upgrade-proposal-release-tag').should( 'have.text', - 'Vega release tagv1' + 'Vega release tag: v1' ); cy.getByTestId('protocol-upgrade-proposal-block-height').should( 'have.text', - 'Upgrade block height2015942' + 'Upgrade block height: 2015942' ); cy.getByTestId('protocol-upgrade-proposal-status').should( 'have.text', diff --git a/apps/governance/src/components/connect-to-vega/connect-to-vega.tsx b/apps/governance/src/components/connect-to-vega/connect-to-vega.tsx index 5aa524ef9..c348f3371 100644 --- a/apps/governance/src/components/connect-to-vega/connect-to-vega.tsx +++ b/apps/governance/src/components/connect-to-vega/connect-to-vega.tsx @@ -23,6 +23,7 @@ export const ConnectToVega = () => { openVegaWalletDialog(); }} data-testid="connect-to-vega-wallet-btn" + variant="primary" > {t('connectVegaWallet')} diff --git a/apps/governance/src/components/heading/heading.tsx b/apps/governance/src/components/heading/heading.tsx index c85771b4d..1edff0a1f 100644 --- a/apps/governance/src/components/heading/heading.tsx +++ b/apps/governance/src/components/heading/heading.tsx @@ -23,7 +23,7 @@ export const Heading = ({ })} >

{ const { t } = useTranslation(); - let className = 'text-white'; + let proposalStatus: ReactNode; + let variant = 'tertiary' as ProposalInfoLabelVariant; - if ( - proposal?.state === Schema.ProposalState.STATE_DECLINED || - proposal?.state === Schema.ProposalState.STATE_FAILED || - proposal?.state === Schema.ProposalState.STATE_REJECTED - ) { - className = 'text-danger'; - } else if ( - proposal?.state === Schema.ProposalState.STATE_ENACTED || - proposal?.state === Schema.ProposalState.STATE_PASSED - ) { - className = 'text-white'; + switch (proposal?.state) { + case ProposalState.STATE_ENACTED: { + proposalStatus = ( + <> + {t('voteState_Enacted')} + + + ); + break; + } + case ProposalState.STATE_PASSED: { + proposalStatus = ( + <> + {t('voteState_Passed')} + + + ); + break; + } + case ProposalState.STATE_WAITING_FOR_NODE_VOTE: { + proposalStatus = ( + <> + {t('voteState_WaitingForNodeVote')} + + + ); + break; + } + case ProposalState.STATE_OPEN: { + variant = 'primary' as ProposalInfoLabelVariant; + proposalStatus = <>{t('voteState_Open')}; + break; + } + case ProposalState.STATE_DECLINED: { + proposalStatus = ( + <> + {t('voteState_Declined')} + + + ); + break; + } + case ProposalState.STATE_REJECTED: { + proposalStatus = ( + <> + {t('voteState_Rejected')} + + + ); + break; + } } - return {t(`${proposal?.state}`)}; + + return ( + {proposalStatus} + ); }; diff --git a/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.spec.tsx b/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.spec.tsx index 1b538be80..cb28a6639 100644 --- a/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.spec.tsx +++ b/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.spec.tsx @@ -16,17 +16,12 @@ it('Renders all data for table', () => { render(); expect(screen.getByText('ID')).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( formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime)) ) ).toBeInTheDocument(); - expect(screen.getByText('Proposed enactment')).toBeInTheDocument(); expect( screen.getByText( @@ -35,17 +30,12 @@ it('Renders all data for table', () => { ) ) ).toBeInTheDocument(); - expect(screen.getByText('Proposed by')).toBeInTheDocument(); expect(screen.getByText(proposal?.party.id ?? '')).toBeInTheDocument(); - expect(screen.getByText('Proposed on')).toBeInTheDocument(); expect( screen.getByText(formatDateWithLocalTimezone(new Date(proposal?.datetime))) ).toBeInTheDocument(); - - expect(screen.getByText('Type')).toBeInTheDocument(); - expect(screen.getByText('Network parameter')).toBeInTheDocument(); }); 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, }); render(); - - expect(screen.getByText('State')).toBeInTheDocument(); - expect(screen.getByText('Enacted')).toBeInTheDocument(); - expect(screen.getByText('Closed on')).toBeInTheDocument(); expect( screen.getByText( formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime)) ) ).toBeInTheDocument(); - expect(screen.getByText('Enacted on')).toBeInTheDocument(); expect( screen.getByText( @@ -104,7 +89,6 @@ it('Renders error details and rejection reason if present', () => { render(); expect(screen.getByText('Error details')).toBeInTheDocument(); expect(screen.getByText(errorDetails)).toBeInTheDocument(); - expect(screen.getByText('Rejection reason')).toBeInTheDocument(); expect( screen.getByText(ProposalRejectionReason.PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE) diff --git a/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.tsx b/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.tsx index fd10d8537..45457a689 100644 --- a/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.tsx +++ b/apps/governance/src/routes/proposals/components/proposal-change-table/proposal-change-table.tsx @@ -6,7 +6,6 @@ import { KeyValueTableRow, RoundedWrapper, } from '@vegaprotocol/ui-toolkit'; -import { CurrentProposalState } from '../current-proposal-state'; import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals'; import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; @@ -20,16 +19,12 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => { const terms = proposal?.terms; return ( - + {t('id')} {proposal?.id} - - {t('state')} - - {isFuture(new Date(terms?.closingDatetime)) ? t('closesOn') @@ -50,26 +45,24 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => { {t('proposedBy')} {proposal?.party.id} - + {t('proposedOn')} {formatDateWithLocalTimezone(new Date(proposal?.datetime))} {proposal?.rejectionReason ? ( - + {t('rejectionReason')} {proposal.rejectionReason} ) : null} {proposal?.errorDetails ? ( - + {t('errorDetails')} {proposal.errorDetails} ) : null} - - {t('type')} - {t(`${proposal?.terms.change.__typename}`)} - ); diff --git a/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.spec.tsx b/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.spec.tsx index a1fb2c7e1..96d18c079 100644 --- a/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.spec.tsx +++ b/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.spec.tsx @@ -1,41 +1,45 @@ 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 type { ProposalQuery } from '../../proposal/__generated__/Proposal'; +import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types'; +import { lastWeek, nextWeek } from '../../test-helpers/mocks'; const renderComponent = ( proposal: ProposalQuery['proposal'], isListItem = true -) => ; +) => render(); describe('Proposal header', () => { it('Renders New market proposal', () => { - render( - renderComponent( - generateProposal({ - rationale: { - title: 'New some market', - description: 'A new some market', - }, - terms: { - change: { - __typename: 'NewMarket', - instrument: { - __typename: 'InstrumentConfiguration', - name: 'Some market', - code: 'FX:BTCUSD/DEC99', - futureProduct: { - __typename: 'FutureProduct', - settlementAsset: { - __typename: 'Asset', - symbol: 'tGBP', - }, + renderComponent( + generateProposal({ + rationale: { + title: 'New some market', + description: 'A new some market', + }, + terms: { + change: { + __typename: 'NewMarket', + instrument: { + __typename: 'InstrumentConfiguration', + name: 'Some market', + code: 'FX:BTCUSD/DEC99', + futureProduct: { + __typename: 'FutureProduct', + settlementAsset: { + __typename: 'Asset', + symbol: 'tGBP', }, }, }, }, - }) - ) + }, + }) ); expect(screen.getByTestId('proposal-title')).toHaveTextContent( 'New some market' @@ -47,20 +51,18 @@ describe('Proposal header', () => { }); it('Renders Update market proposal', () => { - render( - renderComponent( - generateProposal({ - rationale: { - title: 'New market id', + renderComponent( + generateProposal({ + rationale: { + title: 'New market id', + }, + terms: { + change: { + __typename: 'UpdateMarket', + marketId: 'MarketId', }, - terms: { - change: { - __typename: 'UpdateMarket', - marketId: 'MarketId', - }, - }, - }) - ) + }, + }) ); expect(screen.getByTestId('proposal-title')).toHaveTextContent( 'New market id' @@ -77,53 +79,49 @@ describe('Proposal header', () => { }); it('Renders New asset proposal - ERC20', () => { - render( - renderComponent( - generateProposal({ - rationale: { - title: 'New asset: Fake currency', - description: '', - }, - terms: { - change: { - __typename: 'NewAsset', - name: 'Fake currency', - symbol: 'FAKE', - source: { - __typename: 'ERC20', - contractAddress: '0x0', - }, + renderComponent( + generateProposal({ + rationale: { + title: 'New asset: Fake currency', + description: '', + }, + terms: { + change: { + __typename: 'NewAsset', + name: 'Fake currency', + symbol: 'FAKE', + source: { + __typename: 'ERC20', + contractAddress: '0x0', }, }, - }) - ) + }, + }) ); expect(screen.getByTestId('proposal-title')).toHaveTextContent( 'New asset: Fake currency' ); expect(screen.getByTestId('proposal-type')).toHaveTextContent('New asset'); expect(screen.getByTestId('proposal-details')).toHaveTextContent( - 'Symbol: FAKE. ERC20 0x0' + 'Symbol: FAKE. ERC20 contract address: 0x0' ); }); it('Renders New asset proposal - BuiltInAsset', () => { - render( - renderComponent( - generateProposal({ - terms: { - change: { - __typename: 'NewAsset', - name: 'Fake currency', - symbol: 'BIA', - source: { - __typename: 'BuiltinAsset', - maxFaucetAmountMint: '300', - }, + renderComponent( + generateProposal({ + terms: { + change: { + __typename: 'NewAsset', + name: 'Fake currency', + symbol: 'BIA', + source: { + __typename: 'BuiltinAsset', + maxFaucetAmountMint: '300', }, }, - }) - ) + }, + }) ); expect(screen.getByTestId('proposal-title')).toHaveTextContent( 'Unknown proposal' @@ -135,24 +133,22 @@ describe('Proposal header', () => { }); it('Renders Update network', () => { - render( - renderComponent( - generateProposal({ - rationale: { - title: 'Network parameter', - }, - terms: { - change: { - __typename: 'UpdateNetworkParameter', - networkParameter: { - __typename: 'NetworkParameter', - key: 'Network key', - value: 'Network value', - }, + renderComponent( + generateProposal({ + rationale: { + title: 'Network parameter', + }, + terms: { + change: { + __typename: 'UpdateNetworkParameter', + networkParameter: { + __typename: 'NetworkParameter', + key: 'Network key', + value: 'Network value', }, }, - }) - ) + }, + }) ); expect(screen.getByTestId('proposal-title')).toHaveTextContent( 'Network parameter' @@ -165,47 +161,42 @@ describe('Proposal header', () => { ); }); - it('Renders Freeform network - short rationale', () => { - render( - renderComponent( - generateProposal({ - id: 'short', - rationale: { - title: '0x0', + it('Renders Freeform proposal - short rationale', () => { + renderComponent( + generateProposal({ + id: 'short', + rationale: { + title: '0x0', + }, + terms: { + change: { + __typename: 'NewFreeform', }, - terms: { - change: { - __typename: 'NewFreeform', - }, - }, - }) - ) + }, + }) ); expect(screen.getByTestId('proposal-title')).toHaveTextContent('0x0'); expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform'); expect( screen.queryByTestId('proposal-description') ).not.toBeInTheDocument(); - expect(screen.getByTestId('proposal-details')).toHaveTextContent('short'); }); it('Renders Freeform proposal - long rationale (105 chars) - listing', () => { - render( - renderComponent( - generateProposal({ - id: 'long', - rationale: { - title: '0x0', - description: - 'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.', + renderComponent( + generateProposal({ + id: 'long', + rationale: { + title: '0x0', + description: + '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-type')).toHaveTextContent('Freeform'); @@ -213,27 +204,24 @@ describe('Proposal header', () => { expect( screen.queryByTestId('proposal-description') ).not.toBeInTheDocument(); - expect(screen.getByTestId('proposal-details')).toHaveTextContent('long'); }); it('Renders Freeform proposal - long rationale (105 chars) - details', () => { - render( - renderComponent( - generateProposal({ - id: 'long', - rationale: { - title: '0x0', - description: - 'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.', + renderComponent( + generateProposal({ + id: 'long', + rationale: { + title: '0x0', + description: + '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( /Class aptent/ @@ -242,43 +230,36 @@ describe('Proposal header', () => { // Remove once proposals have rationale and re-enable above tests it('Renders Freeform proposal - id for title', () => { - render( - renderComponent( - generateProposal({ - id: 'freeform id', - rationale: { - title: 'freeform', + renderComponent( + generateProposal({ + id: 'freeform id', + rationale: { + title: 'freeform', + }, + terms: { + change: { + __typename: 'NewFreeform', }, - terms: { - change: { - __typename: 'NewFreeform', - }, - }, - }) - ) + }, + }) ); expect(screen.getByTestId('proposal-title')).toHaveTextContent('freeform'); expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform'); expect( screen.queryByTestId('proposal-description') ).not.toBeInTheDocument(); - expect(screen.queryByTestId('proposal-details')).toHaveTextContent( - 'freeform id' - ); }); it('Renders asset change proposal header', () => { - render( - renderComponent( - generateProposal({ - terms: { - change: { - __typename: 'UpdateAsset', - assetId: 'foo', - }, + renderComponent( + generateProposal({ + terms: { + change: { + __typename: 'UpdateAsset', + assetId: 'foo', }, - }) - ) + }, + }) ); expect(screen.getByTestId('proposal-type')).toHaveTextContent( 'Update asset' @@ -287,20 +268,104 @@ describe('Proposal header', () => { }); it("Renders unknown proposal if it's a different proposal type", () => { - render( - renderComponent( - generateProposal({ - terms: { - change: { - // @ts-ignore unknown proposal - __typename: 'Foo', - }, + renderComponent( + generateProposal({ + terms: { + change: { + // @ts-ignore unknown proposal + __typename: 'Foo', }, - }) - ) + }, + }) ); expect(screen.getByTestId('proposal-title')).toHaveTextContent( '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'); + }); }); diff --git a/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.tsx b/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.tsx index 2877e9cc7..38c1bffae 100644 --- a/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.tsx +++ b/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next'; -import { Intent, Lozenge } from '@vegaprotocol/ui-toolkit'; +import { Lozenge } from '@vegaprotocol/ui-toolkit'; import { shorten } from '@vegaprotocol/utils'; import { Heading, SubHeading } from '../../../../components/heading'; import type { ReactNode } from 'react'; @@ -7,6 +7,8 @@ import type { ProposalFieldsFragment } from '../../proposals/__generated__/Propo import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; import ReactMarkdown from 'react-markdown'; import { truncateMiddle } from '../../../../lib/truncate-middle'; +import { CurrentProposalState } from '../current-proposal-state'; +import { ProposalInfoLabel } from '../proposal-info-label'; export const ProposalHeader = ({ proposal, @@ -19,7 +21,7 @@ export const ProposalHeader = ({ const change = proposal?.terms.change; let details: ReactNode; - let proposalType: ReactNode; + let proposalType = ''; let title = proposal?.rationale.title.trim(); let description = proposal?.rationale.description.trim(); @@ -32,10 +34,12 @@ export const ProposalHeader = ({ switch (change?.__typename) { case 'NewMarket': { - proposalType = t('NewMarket'); + proposalType = 'NewMarket'; details = ( <> - {t('Code')}: {change.instrument.code}.{' '} + + {t('Code')}: {change.instrument.code}. + {' '} {change.instrument.futureProduct?.settlementAsset.symbol ? ( <> @@ -51,53 +55,60 @@ export const ProposalHeader = ({ break; } case 'UpdateMarket': { - proposalType = t('UpdateMarket'); - details = `${t('Market change')}: ${change.marketId}`; + proposalType = 'UpdateMarket'; + details = ( + <> + {t('Market change')}:{' '} + {truncateMiddle(change.marketId)} + + ); break; } case 'NewAsset': { - proposalType = t('NewAsset'); + proposalType = 'NewAsset'; details = ( <> - {t('Symbol')}: {change.symbol}.{' '} - - {change.source.__typename === 'ERC20' && - `ERC20 ${change.source.contractAddress}`} - {change.source.__typename === 'BuiltinAsset' && - `${t('Max faucet amount mint')}: ${ - change.source.maxFaucetAmountMint - }`} - + {t('Symbol')}: {change.symbol}.{' '} + {change.source.__typename === 'ERC20' && ( + <> + {t('ERC20ContractAddress')}:{' '} + {change.source.contractAddress} + + )}{' '} + {change.source.__typename === 'BuiltinAsset' && ( + <> + {t('MaxFaucetAmountMint')}:{' '} + {change.source.maxFaucetAmountMint} + + )} ); break; } case 'UpdateNetworkParameter': { - proposalType = t('NetworkParameter'); - const parametersClasses = 'font-mono leading-none'; + proposalType = 'NetworkParameter'; details = ( <> - - {change.networkParameter.key} - {' '} - {t('to')}{' '} - - {change.networkParameter.value} + {t('Change')}:{' '} + {change.networkParameter.key}{' '} + {t('to')}{' '} + + {change.networkParameter.value} ); break; } case 'NewFreeform': { - proposalType = t('Freeform'); - details = `${t('FreeformProposal')}: ${proposal?.id}`; + proposalType = 'Freeform'; + details = ; break; } case 'UpdateAsset': { - proposalType = t('UpdateAsset'); + proposalType = 'UpdateAsset'; details = ( <> - {t('Asset ID')}: + {t('AssetID')}:{' '} {truncateMiddle(change.assetId)} ); @@ -106,7 +117,7 @@ export const ProposalHeader = ({ } return ( -
+ <>
{isListItem ? (
@@ -118,30 +129,39 @@ export const ProposalHeader = ({
- {proposalType && ( -
- {proposalType} -
- )} -
-
- {description && !isListItem && ( -
- - {description} - -
- )} +
+ + {t(`${proposalType}`)} + +
+ +
+ +
- {details &&
{details}
} -
+ {details && ( +
+ {details} +
+ )} + + {description && !isListItem && ( +
+ {/*
{t('ProposalDescription')}:
*/} + + + {description} + +
+ )} + ); }; diff --git a/apps/governance/src/routes/proposals/components/proposal-info-label/index.tsx b/apps/governance/src/routes/proposals/components/proposal-info-label/index.tsx new file mode 100644 index 000000000..6198befef --- /dev/null +++ b/apps/governance/src/routes/proposals/components/proposal-info-label/index.tsx @@ -0,0 +1 @@ +export * from './proposal-info-label'; diff --git a/apps/governance/src/routes/proposals/components/proposal-info-label/proposal-info-label.tsx b/apps/governance/src/routes/proposals/components/proposal-info-label/proposal-info-label.tsx new file mode 100644 index 000000000..ec5f774ee --- /dev/null +++ b/apps/governance/src/routes/proposals/components/proposal-info-label/proposal-info-label.tsx @@ -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
{children}
; +}; diff --git a/apps/governance/src/routes/proposals/components/proposal-terms-json/proposal-terms-json.tsx b/apps/governance/src/routes/proposals/components/proposal-terms-json/proposal-terms-json.tsx index 9b8c9d8cc..a2c8e993f 100644 --- a/apps/governance/src/routes/proposals/components/proposal-terms-json/proposal-terms-json.tsx +++ b/apps/governance/src/routes/proposals/components/proposal-terms-json/proposal-terms-json.tsx @@ -1,8 +1,10 @@ import { useTranslation } from 'react-i18next'; -import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; +import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import { SubHeading } from '../../../../components/heading'; import type { PartialDeep } from 'type-fest'; import type * as Schema from '@vegaprotocol/types'; +import { useState } from 'react'; +import classnames from 'classnames'; export const ProposalTermsJson = ({ terms, @@ -10,10 +12,26 @@ export const ProposalTermsJson = ({ terms: PartialDeep; }) => { const { t } = useTranslation(); + const [showDetails, setShowDetails] = useState(false); + const showDetailsIconClasses = classnames('mb-4', { + 'rotate-180': showDetails, + }); + return (
- - + + + {showDetails && }
); }; diff --git a/apps/governance/src/routes/proposals/components/proposal-votes-table/proposal-votes-table.spec.tsx b/apps/governance/src/routes/proposals/components/proposal-votes-table/proposal-votes-table.spec.tsx index 2ee8764df..6edce2761 100644 --- a/apps/governance/src/routes/proposals/components/proposal-votes-table/proposal-votes-table.spec.tsx +++ b/apps/governance/src/routes/proposals/components/proposal-votes-table/proposal-votes-table.spec.tsx @@ -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 { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; 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', () => { renderComponent(); + fireEvent.click(screen.getByTestId('vote-breakdown-toggle')); expect(screen.getByText('Expected to pass')).toBeInTheDocument(); expect(screen.getByText('Token majority 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('Majority Required')).toBeInTheDocument(); expect(screen.getByText('Number of voting parties')).toBeInTheDocument(); - expect(screen.getByText('Total yes tokens')).toBeInTheDocument(); + expect(screen.getByText('Total tokens voted')).toBeInTheDocument(); expect( screen.getByText('Total tokens voted percentage') ).toBeInTheDocument(); @@ -70,13 +71,14 @@ describe('Proposal Votes Table', () => { it('displays different breakdown fields for update market proposal', () => { renderComponent(updateMarketProposal, updateMarketProposalType); + fireEvent.click(screen.getByTestId('vote-breakdown-toggle')); expect(screen.getByText('Liquidity majority met')).toBeInTheDocument(); expect(screen.getByText('Liquidity participation met')).toBeInTheDocument(); expect( screen.getByText('Liquidity shares for proposal') ).toBeInTheDocument(); 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('Number of votes for')).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', () => { renderComponent(updateMarketProposal, updateMarketProposalType); + fireEvent.click(screen.getByTestId('vote-breakdown-toggle')); expect(screen.getByText('👍 by token vote')).toBeInTheDocument(); }); @@ -110,6 +113,7 @@ describe('Proposal Votes Table', () => { }), updateMarketProposalType ); + fireEvent.click(screen.getByTestId('vote-breakdown-toggle')); expect(screen.getByText('👍 by liquidity vote')).toBeInTheDocument(); }); }); diff --git a/apps/governance/src/routes/proposals/components/proposal-votes-table/proposal-votes-table.tsx b/apps/governance/src/routes/proposals/components/proposal-votes-table/proposal-votes-table.tsx index d15ce463a..12a58edf8 100644 --- a/apps/governance/src/routes/proposals/components/proposal-votes-table/proposal-votes-table.tsx +++ b/apps/governance/src/routes/proposals/components/proposal-votes-table/proposal-votes-table.tsx @@ -1,9 +1,12 @@ +import classnames from 'classnames'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { KeyValueTable, KeyValueTableRow, Thumbs, RoundedWrapper, + Icon, } from '@vegaprotocol/ui-toolkit'; import { formatNumber, formatNumberPercentage } from '@vegaprotocol/utils'; import { SubHeading } from '../../../../components/heading'; @@ -26,6 +29,7 @@ export const ProposalVotesTable = ({ const { appState: { totalSupply }, } = useAppState(); + const [showDetails, setShowDetails] = useState(false); const { willPassByTokenVote, willPassByLPVote, @@ -53,113 +57,130 @@ export const ProposalVotesTable = ({ ? t('byTokenVote') : t('byLiquidityVote'); + const showDetailsIconClasses = classnames('mb-4', { + 'rotate-180': showDetails, + }); + return ( <> - - - - - {t('expectedToPass')} - {isUpdateMarket ? ( - updateMarketWillPass ? ( - - ) : ( - - ) - ) : willPassByTokenVote ? ( - - ) : ( - - )} - - - {t('majorityMet')} - {majorityMet ? : } - - {isUpdateMarket && ( + + + {showDetails && ( + + - {t('majorityLPMet')} - {majorityLPMet ? : } - - )} - - {t('participationMet')} - {participationMet ? : } - - {isUpdateMarket && ( - - {t('participationLPMet')} - {participationLPMet ? ( + {t('expectedToPass')} + {isUpdateMarket ? ( + updateMarketWillPass ? ( + + ) : ( + + ) + ) : willPassByTokenVote ? ( ) : ( )} - )} - - {t('tokenForProposal')} - {formatNumber(yesTokens, 2)} - - {isUpdateMarket && ( - {t('tokenLPForProposal')} - {formatNumber(yesEquityLikeShareWeight, 2)} + {t('majorityMet')} + {majorityMet ? : } - )} - - {t('totalSupply')} - {formatNumber(totalSupply, 2)} - - - {t('tokensAgainstProposal')} - {formatNumber(noTokens, 2)} - - - {t('participationRequired')} - {formatNumberPercentage(requiredParticipation)} - - - {t('majorityRequired')} - {formatNumberPercentage(requiredMajorityPercentage)} - - {!isUpdateMarket && ( - <> + {isUpdateMarket && ( - {t('numberOfVotingParties')} - {formatNumber(totalVotes, 0)} + {t('majorityLPMet')} + {majorityLPMet ? : } + )} + + {t('participationMet')} + {participationMet ? : } + + {isUpdateMarket && ( - {t('totalTokensVotes')} - {formatNumber(totalTokensVoted, 2)} + {t('participationLPMet')} + {participationLPMet ? ( + + ) : ( + + )} + )} + + {t('tokenForProposal')} + {formatNumber(yesTokens, 2)} + + {isUpdateMarket && ( - {t('totalTokenVotedPercentage')} - {formatNumberPercentage(totalTokensPercentage, 2)} + {t('tokenLPForProposal')} + {formatNumber(yesEquityLikeShareWeight, 2)} - - {t('numberOfForVotes')} - {formatNumber(yesVotes, 0)} - - - {t('numberOfAgainstVotes')} - {formatNumber(noVotes, 0)} - - - {t('yesPercentage')} - {formatNumberPercentage(yesPercentage, 2)} - - - {t('noPercentage')} - {formatNumberPercentage(noPercentage, 2)} - - - )} - - + )} + + {t('totalSupply')} + {formatNumber(totalSupply, 2)} + + + {t('tokensAgainstProposal')} + {formatNumber(noTokens, 2)} + + + {t('participationRequired')} + {formatNumberPercentage(requiredParticipation)} + + + {t('majorityRequired')} + {formatNumberPercentage(requiredMajorityPercentage)} + + {!isUpdateMarket && ( + <> + + {t('numberOfVotingParties')} + {formatNumber(totalVotes, 0)} + + + {t('totalTokensVotes')} + {formatNumber(totalTokensVoted, 2)} + + + {t('totalTokenVotedPercentage')} + {formatNumberPercentage(totalTokensPercentage, 2)} + + + {t('numberOfForVotes')} + {formatNumber(yesVotes, 0)} + + + {t('numberOfAgainstVotes')} + {formatNumber(noVotes, 0)} + + + {t('yesPercentage')} + {formatNumberPercentage(yesPercentage, 2)} + + + {t('noPercentage')} + {formatNumberPercentage(noPercentage, 2)} + + + )} + + + )} ); }; diff --git a/apps/governance/src/routes/proposals/components/proposal/proposal.tsx b/apps/governance/src/routes/proposals/components/proposal/proposal.tsx index e0aa51190..598ae76cc 100644 --- a/apps/governance/src/routes/proposals/components/proposal/proposal.tsx +++ b/apps/governance/src/routes/proposals/components/proposal/proposal.tsx @@ -2,7 +2,7 @@ import { NetworkParams, useNetworkParams, } 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 type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals'; import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; @@ -78,7 +78,7 @@ export const Proposal = ({ proposal }: ProposalProps) => {
-
+
{proposal.terms.change.__typename === 'NewAsset' && @@ -91,14 +91,18 @@ export const Proposal = ({ proposal }: ProposalProps) => { /> ) : null}
- + + +
-
+
diff --git a/apps/governance/src/routes/proposals/components/proposals-list-item/proposals-list-item-details.spec.tsx b/apps/governance/src/routes/proposals/components/proposals-list-item/proposals-list-item-details.spec.tsx index 7bbdbe608..f45b7d784 100644 --- a/apps/governance/src/routes/proposals/components/proposals-list-item/proposals-list-item-details.spec.tsx +++ b/apps/governance/src/routes/proposals/components/proposals-list-item/proposals-list-item-details.spec.tsx @@ -97,7 +97,6 @@ describe('Proposals list item details', () => { }, }) ); - expect(screen.getByTestId('proposal-status')).toHaveTextContent('Enacted'); expect(screen.getByTestId('vote-details')).toHaveTextContent( 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( `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( `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( '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( '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( '5 days left to vote' ); @@ -268,10 +260,7 @@ describe('Proposals list item details', () => { networkParamsQueryMock, createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_YES), ]); - expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open'); - - expect(await screen.findByText('You voted')).toBeInTheDocument(); - expect(await screen.findByText('For')).toBeInTheDocument(); + expect(await screen.findByText('You voted For')).toBeInTheDocument(); }); it('Renders proposal state: Open - user voted against', async () => { @@ -285,9 +274,7 @@ describe('Proposals list item details', () => { networkParamsQueryMock, createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_NO), ]); - expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open'); - expect(await screen.findByText('You voted')).toBeInTheDocument(); - expect(await screen.findByText('Against')).toBeInTheDocument(); + expect(await screen.findByText('You voted Against')).toBeInTheDocument(); }); 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( '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( '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'); }); @@ -359,7 +343,6 @@ describe('Proposals list item details', () => { }, }) ); - expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined'); expect(screen.getByTestId('vote-status')).toHaveTextContent( '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( 'Majority not reached' ); @@ -395,7 +377,6 @@ describe('Proposals list item details', () => { ProposalRejectionReason.PROPOSAL_ERROR_INVALID_FUTURE_PRODUCT, }) ); - expect(screen.getByTestId('proposal-status')).toHaveTextContent('Rejected'); expect(screen.getByTestId('vote-status')).toHaveTextContent( 'Invalid future product' ); diff --git a/apps/governance/src/routes/proposals/components/proposals-list-item/proposals-list-item-details.tsx b/apps/governance/src/routes/proposals/components/proposals-list-item/proposals-list-item-details.tsx index 181d58efd..d807981c5 100644 --- a/apps/governance/src/routes/proposals/components/proposals-list-item/proposals-list-item-details.tsx +++ b/apps/governance/src/routes/proposals/components/proposals-list-item/proposals-list-item-details.tsx @@ -1,11 +1,8 @@ import { Link } from 'react-router-dom'; -import { Button, Icon } from '@vegaprotocol/ui-toolkit'; +import { Button } from '@vegaprotocol/ui-toolkit'; import { useVoteInformation } from '../../hooks'; import { useUserVote } from '../vote-details/use-user-vote'; -import { - StatusPass, - StatusFail, -} from '../current-proposal-status/current-proposal-status'; +import { StatusPass } from '../current-proposal-status/current-proposal-status'; import { format, formatDistanceToNowStrict } from 'date-fns'; import { useTranslation } from 'react-i18next'; import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats'; @@ -22,7 +19,7 @@ const MajorityNotReached = () => { const { t } = useTranslation(); return ( <> - {t('Majority')} {t('not reached')} + {t('Majority')} {t('not reached')} ); }; @@ -30,7 +27,7 @@ const ParticipationNotReached = () => { const { t } = useTranslation(); return ( <> - {t('Participation')} {t('not reached')} + {t('Participation')} {t('not reached')} ); }; @@ -57,17 +54,11 @@ export const ProposalsListItemDetails = ({ ? t('byTokenVote') : t('byLPVote'); - let proposalStatus: ReactNode; let voteDetails: ReactNode; let voteStatus: ReactNode; switch (state) { case ProposalState.STATE_ENACTED: { - proposalStatus = ( - <> - {t('voteState_Enacted')} - - ); voteDetails = proposal?.terms.enactmentDatetime && ( <> {format( @@ -79,11 +70,6 @@ export const ProposalsListItemDetails = ({ break; } case ProposalState.STATE_PASSED: { - proposalStatus = ( - <> - {t('voteState_Passed')} - - ); voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && ( <> {t('toEnactOn')}{' '} @@ -97,11 +83,6 @@ export const ProposalsListItemDetails = ({ break; } case ProposalState.STATE_WAITING_FOR_NODE_VOTE: { - proposalStatus = ( - <> - {t('voteState_WaitingForNodeVote')} - - ); voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && ( <> {t('toEnactOn')}{' '} @@ -115,19 +96,14 @@ export const ProposalsListItemDetails = ({ break; } case ProposalState.STATE_OPEN: { - proposalStatus = ( - <> - {t('voteState_Open')} - - ); voteDetails = (voteState === 'Yes' && ( <> - {t('youVoted')} {t('voteState_Yes')} + {t('youVoted')} {t('voteState_Yes')} )) || (voteState === 'No' && ( <> - {t('youVoted')} {t('voteState_No')} + {t('youVoted')} {t('voteState_No')} )) || ( <> @@ -148,40 +124,29 @@ export const ProposalsListItemDetails = ({ ) : ( <> - {t('Set to')} {t('fail')} + {t('Set to')} {t('fail')} ))) || (!participationMet && ) || (!majorityMet && ) || (willPassByTokenVote ? ( <> - {t('Set to')} {t('pass')} + {t('Set to')} {t('pass')} ) : ( <> - {t('Set to')} {t('fail')} + {t('Set to')} {t('fail')} )); break; } case ProposalState.STATE_DECLINED: { - proposalStatus = ( - <> - {t('voteState_Declined')} - - ); voteStatus = (!participationMet && ) || (!majorityMet && ); break; } case ProposalState.STATE_REJECTED: { - proposalStatus = ( - <> - {t('voteState_Rejected')}{' '} - - - ); voteStatus = proposal?.rejectionReason && ( <>{t(ProposalRejectionReasonMapping[proposal.rejectionReason])} ); @@ -190,16 +155,10 @@ export const ProposalsListItemDetails = ({ } return ( -
-
- {proposalStatus} -
+
{voteDetails && (
{voteDetails} @@ -216,9 +175,7 @@ export const ProposalsListItemDetails = ({ {proposal?.id && (
- +
)} diff --git a/apps/governance/src/routes/proposals/components/proposals-list/proposals-list.tsx b/apps/governance/src/routes/proposals/components/proposals-list/proposals-list.tsx index c6b8e761f..5af38356d 100644 --- a/apps/governance/src/routes/proposals/components/proposals-list/proposals-list.tsx +++ b/apps/governance/src/routes/proposals/components/proposals-list/proposals-list.tsx @@ -106,7 +106,7 @@ export const ProposalsList = ({ {proposals.length > 0 && ( )} -
+
{sortedProposals.open.length > 0 || sortedProtocolUpgradeProposals.open.length > 0 ? ( diff --git a/apps/governance/src/routes/proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item.tsx b/apps/governance/src/routes/proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item.tsx index e96fb5941..c94101df5 100644 --- a/apps/governance/src/routes/proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item.tsx +++ b/apps/governance/src/routes/proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item.tsx @@ -3,15 +3,15 @@ import { Link } from 'react-router-dom'; import { Button, Icon, - Intent, Lozenge, RoundedWrapper, } from '@vegaprotocol/ui-toolkit'; import { stripFullStops } from '@vegaprotocol/utils'; import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types'; import { SubHeading } from '../../../../components/heading'; -import type { ReactNode } from 'react'; +import { ProposalInfoLabel } from '../proposal-info-label'; import Routes from '../../../routes'; +import type { ReactNode } from 'react'; import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals'; interface ProtocolProposalsListItemProps { @@ -29,30 +29,30 @@ export const ProtocolUpgradeProposalsListItem = ({ switch (proposal.status) { case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED: proposalStatusIcon = ( -
+ -
+ ); break; case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING: proposalStatusIcon = ( -
+ -
+ ); break; case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED: proposalStatusIcon = ( -
+ -
+ ); break; case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED: proposalStatusIcon = ( -
+ -
+ ); break; } @@ -71,18 +71,28 @@ export const ProtocolUpgradeProposalsListItem = ({
-
- {t('networkUpgrade')} +
+
+ + {t('networkUpgrade')} + +
+ +
+ + {t(`${proposal.status}`)} {proposalStatusIcon} + +
- {t('vegaReleaseTag')} + {t('vegaReleaseTag')}:{' '} {proposal.vegaReleaseTag}
@@ -90,30 +100,18 @@ export const ProtocolUpgradeProposalsListItem = ({ data-testid="protocol-upgrade-proposal-block-height" className="mb-2" > - {t('upgradeBlockHeight')} + {t('upgradeBlockHeight')}:{' '} {proposal.upgradeBlockHeight}
-
-
-
- {t(`${proposal.status}`)} - {proposalStatusIcon} -
-
- -
+
+
- +
diff --git a/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.tsx b/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.tsx index 106f5c575..3101758f6 100644 --- a/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.tsx +++ b/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.tsx @@ -107,10 +107,6 @@ export const VoteButtons = ({ ); } - if (currentStakeAvailable.isLessThanOrEqualTo(0)) { - return t('noGovernanceTokens'); - } - if (minVoterBalance && spamProtectionMinTokens) { const formattedMinVoterBalance = new BigNumber( addDecimal(minVoterBalance, 18) @@ -163,24 +159,30 @@ export const VoteButtons = ({ return ( <> {changeVote || (voteState === VoteState.NotCast && proposalVotable) ? ( -
-
+ <> + {currentStakeAvailable.isLessThanOrEqualTo(0) && ( +

{t('noGovernanceTokens')}

+ )} + +
-
-
-
+ ) : ( (voteState === VoteState.Yes || voteState === VoteState.No) && (

diff --git a/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx b/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx index db91ea186..19746d9c2 100644 --- a/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx +++ b/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx @@ -1,5 +1,6 @@ import { useTranslation } from 'react-i18next'; import { formatDistanceToNow } from 'date-fns'; +import { RoundedWrapper, Icon } from '@vegaprotocol/ui-toolkit'; import { useVegaWallet } from '@vegaprotocol/wallet'; import { ProposalState } from '@vegaprotocol/types'; import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals'; @@ -199,10 +200,11 @@ export const VoteDetails = ({ {proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (

{t('votingThresholdInfo')}

)} - {pubKey ? ( -
- - {proposal && ( + +
+ + {pubKey ? ( + proposal && ( - )} -
- ) : ( - - )} + ) + ) : ( + +
+
+ +
{t('connectAVegaWalletToVote')}
+
+
+ +
+ )} +
); diff --git a/libs/ui-toolkit/src/utils/intent.ts b/libs/ui-toolkit/src/utils/intent.ts index cc5f9ede6..58ca3c7da 100644 --- a/libs/ui-toolkit/src/utils/intent.ts +++ b/libs/ui-toolkit/src/utils/intent.ts @@ -13,7 +13,7 @@ export const getIntentBorder = (intent = Intent.None) => { 'border-warning': intent === Intent.Warning, 'border-neutral-500': intent === Intent.None, 'border-vega-blue-300': intent === Intent.Primary, - 'border-success': intent === Intent.Success, + 'border-vega-green': intent === Intent.Success, }; }; diff --git a/libs/utils/src/lib/format/date.ts b/libs/utils/src/lib/format/date.ts index 46518d9f2..838c91f6a 100644 --- a/libs/utils/src/lib/format/date.ts +++ b/libs/utils/src/lib/format/date.ts @@ -1,6 +1,6 @@ import once from 'lodash/once'; +import { format } from 'date-fns'; import { getUserLocale } from '../get-user-locale'; -import { utcToZonedTime, format as tzFormat } from 'date-fns-tz'; export const isValidDate = (date: Date) => date instanceof Date && !isNaN(date.getTime()); @@ -53,15 +53,24 @@ export const formatForInput = (date: Date) => { 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 */ export const formatDateWithLocalTimezone = ( date: Date, - formatStr = 'dd MMMM yyyy HH:mm (z)' + formatStr = 'dd MMMM yyyy HH:mm' ) => { - const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const localDatetime = utcToZonedTime(date, userTimeZone); - - return tzFormat(localDatetime, formatStr, { - timeZone: userTimeZone, - }); + const formattedDate = format(date, formatStr); + const timeZoneAbbreviation = getTimeZoneAbbreviation(date); + return `${formattedDate} (${timeZoneAbbreviation})`; };