Compare commits

..
325 changed files with 2006 additions and 3990 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api NX_CHAIN_EXPLORER_URL=https://explorer.vega.trading/.netlify/functions/chain-explorer-api
NX_TENDERMINT_URL=https://mainnet-observer-proxy01.ops.vega.xyz/ NX_TENDERMINT_URL=https://mainnet-observer-proxy01.ops.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://mainnet-observer-proxy01.ops.vega.xyz/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://mainnet-observer-proxy01.ops.vega.xyz/websocket
NX_VEGA_URL=https://api.vega.community/graphql NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest
@@ -10,8 +10,6 @@
"governance.proposal.updateMarket.minVoterBalance", "governance.proposal.updateMarket.minVoterBalance",
"governance.proposal.updateNetParam.minProposerBalance", "governance.proposal.updateNetParam.minProposerBalance",
"governance.proposal.updateNetParam.minVoterBalance", "governance.proposal.updateNetParam.minVoterBalance",
"governance.proposal.updateAsset.minProposerBalance",
"governance.proposal.updateAsset.minVoterBalance",
"reward.staking.delegation.maxPayoutPerEpoch", "reward.staking.delegation.maxPayoutPerEpoch",
"reward.staking.delegation.maxPayoutPerParticipant", "reward.staking.delegation.maxPayoutPerParticipant",
"reward.staking.delegation.minimumValidatorStake", "reward.staking.delegation.minimumValidatorStake",
@@ -21,6 +19,9 @@
"validators.delegation.minAmount" "validators.delegation.minAmount"
], ],
"fiveDecimal": [ "fiveDecimal": [
"governance.proposal.updateAsset.minProposerBalance",
"governance.proposal.updateAsset.minVoterBalance",
"governance.proposal.updateAsset.requiredParticipation",
"market.fee.factors.infrastructureFee", "market.fee.factors.infrastructureFee",
"market.fee.factors.makerFee", "market.fee.factors.makerFee",
"market.liquidity.bondPenaltyParameter", "market.liquidity.bondPenaltyParameter",
@@ -76,7 +77,6 @@
"governance.proposal.updateNetParam.requiredMajority", "governance.proposal.updateNetParam.requiredMajority",
"governance.proposal.updateNetParam.requiredParticipation", "governance.proposal.updateNetParam.requiredParticipation",
"governance.proposal.updateMarket.minProposerEquityLikeShare", "governance.proposal.updateMarket.minProposerEquityLikeShare",
"governance.proposal.updateAsset.requiredParticipation",
"validators.vote.required" "validators.vote.required"
], ],
"duration": [ "duration": [
@@ -138,7 +138,7 @@ context('Network parameters page', { tags: '@smoke' }, function () {
}); });
}); });
it.skip('should list each network parameter displayed as a currency value with four decimals - in the correct format', function () { it('should list each network parameter displayed as a currency value with four decimals - in the correct format', function () {
cy.get_network_parameters().then((network_parameters) => { cy.get_network_parameters().then((network_parameters) => {
network_parameters = Object.entries(network_parameters); network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => { network_parameters.forEach((network_parameter) => {
-1
View File
@@ -10,7 +10,6 @@ NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_GOVERNANCE_URL=https://stagnet1.token.vega.xyz NX_VEGA_GOVERNANCE_URL=https://stagnet1.token.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.jsoo NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.jsoo
-1
View File
@@ -4,7 +4,6 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_ENV=CUSTOM NX_VEGA_ENV=CUSTOM
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
# App flags # App flags
NX_EXPLORER_TXS_LIST=0 NX_EXPLORER_TXS_LIST=0
+1 -2
View File
@@ -8,5 +8,4 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=/
+4 -5
View File
@@ -1,10 +1,9 @@
# App configuration variables # App configuration variables
NX_TENDERMINT_URL=https://be.vega.community NX_TENDERMINT_URL=https://be.explorer.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.vega.community/rest/ NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz/
+1 -2
View File
@@ -8,5 +8,4 @@ NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
-1
View File
@@ -11,4 +11,3 @@ NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
NX_VEGA_EXPLORER_URL=https://validator-testnet.explorer.vega.xyz/
-1
View File
@@ -5,4 +5,3 @@ NX_VEGA_ENV=CUSTOM
NX_BLOCK_EXPLORER= NX_BLOCK_EXPLORER=
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
@@ -6,7 +6,6 @@ export type AssetBalanceProps = {
assetId: string; assetId: string;
price: string; price: string;
showAssetLink?: boolean; showAssetLink?: boolean;
showAssetSymbol?: boolean;
}; };
/** /**
@@ -17,21 +16,18 @@ const AssetBalance = ({
assetId, assetId,
price, price,
showAssetLink = true, showAssetLink = true,
showAssetSymbol = false,
}: AssetBalanceProps) => { }: AssetBalanceProps) => {
const { data: asset, loading } = useAssetDataProvider(assetId); const { data: asset } = useAssetDataProvider(assetId);
const label = const label =
!loading && asset && asset.decimals asset && asset.decimals
? addDecimalsFormatNumber(price, asset.decimals) ? addDecimalsFormatNumber(price, asset.decimals)
: price; : price;
return ( return (
<div className="inline-block"> <div className="inline-block">
<span>{label}</span>{' '} <span>{label}</span>{' '}
{showAssetLink && asset?.id ? ( {showAssetLink && asset?.id ? <AssetLink assetId={assetId} /> : null}
<AssetLink showAssetSymbol={showAssetSymbol} assetId={assetId} />
) : null}
</div> </div>
); );
}; };
@@ -13,17 +13,11 @@ const DEFAULT_DECIMALS = 18;
* the governance asset first, which is set by a network parameter * the governance asset first, which is set by a network parameter
*/ */
const GovernanceAssetBalance = ({ price }: GovernanceAssetBalanceProps) => { const GovernanceAssetBalance = ({ price }: GovernanceAssetBalanceProps) => {
const { data, loading } = useExplorerGovernanceAssetQuery(); const { data } = useExplorerGovernanceAssetQuery();
if (!loading && data && data.networkParameter?.value) { if (data && data.networkParameter?.value) {
const governanceAssetId = data.networkParameter.value; const governanceAssetId = data.networkParameter.value;
return ( return <AssetBalance price={price} assetId={governanceAssetId} />;
<AssetBalance
price={price}
showAssetSymbol={true}
assetId={governanceAssetId}
/>
);
} else { } else {
return ( return (
<div className="inline-block"> <div className="inline-block">
@@ -14,72 +14,10 @@ import {
SettlementAssetInfoPanel, SettlementAssetInfoPanel,
} from '@vegaprotocol/market-info'; } from '@vegaprotocol/market-info';
import { MarketInfoTable } from '@vegaprotocol/market-info'; import { MarketInfoTable } from '@vegaprotocol/market-info';
import type { DataSourceDefinition } from '@vegaprotocol/types';
import isEqual from 'lodash/isEqual';
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => { export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
if (!market) return null; if (!market) return null;
const settlementData =
market.tradableInstrument.instrument.product.dataSourceSpecForSettlementData
.data;
const terminationData =
market.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.data;
const getSigners = (data: DataSourceDefinition) => {
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
const signers = data.sourceType.sourceType.signers || [];
return signers.map(({ signer }, i) => {
return (
(signer.__typename === 'ETHAddress' && signer.address) ||
(signer.__typename === 'PubKey' && signer.key)
);
});
}
return [];
};
const oraclePanels = isEqual(
getSigners(settlementData),
getSigners(terminationData)
)
? [
{
title: t('Settlement Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
{
title: t('Termination Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="termination"
/>
),
},
]
: [
{
title: t('Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
];
const panels = [ const panels = [
{ {
title: t('Key details'), title: t('Key details'),
@@ -157,7 +95,22 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
<LiquidityPriceRangeInfoPanel market={market} noBorder={false} /> <LiquidityPriceRangeInfoPanel market={market} noBorder={false} />
), ),
}, },
...oraclePanels, {
title: t('Settlement Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
{
title: t('Termination Oracle'),
content: (
<OracleInfoPanel noBorder={false} market={market} type="termination" />
),
},
]; ];
return ( return (
@@ -20,7 +20,7 @@ export const PageHeader = ({
copy = false, copy = false,
className, className,
}: PageHeaderProps) => { }: PageHeaderProps) => {
const titleClasses = 'text-xl uppercase font-alpha calt'; const titleClasses = 'text-4xl xl:text-5xl uppercase font-alpha calt';
return ( return (
<header className={className}> <header className={className}>
<span className={`${titleClasses} block`}>{prefix}</span> <span className={`${titleClasses} block`}>{prefix}</span>
@@ -12,10 +12,7 @@ import { useLayoutEffect, useMemo, useRef, useState } from 'react';
import type { RowClickedEvent } from 'ag-grid-community'; import type { RowClickedEvent } from 'ag-grid-community';
import { getDateTimeFormat } from '@vegaprotocol/utils'; import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { ProposalStateMapping } from '@vegaprotocol/types'; import { ProposalStateMapping } from '@vegaprotocol/types';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment'; import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
@@ -30,7 +30,7 @@ export const BundleExists = ({
// Note if this is wrong, the wrong decoder will be used which will give incorrect data // Note if this is wrong, the wrong decoder will be used which will give incorrect data
return ( return (
<div className="w-auto h-10 max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5"> <div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
<IconForBundleStatus status={status} /> <IconForBundleStatus status={status} />
<h1 className="text-xl pb-1"> <h1 className="text-xl pb-1">
{status === 'STATUS_ENABLED' {status === 'STATUS_ENABLED'
@@ -1,3 +1,4 @@
export { TxList } from './tx-list';
export { TxOrderType } from './tx-order-type'; export { TxOrderType } from './tx-order-type';
export { TxsInfiniteList } from './txs-infinite-list'; export { TxsInfiniteList } from './txs-infinite-list';
export { TxsInfiniteListItem } from './txs-infinite-list-item'; export { TxsInfiniteListItem } from './txs-infinite-list-item';
@@ -0,0 +1,14 @@
import { t } from '@vegaprotocol/i18n';
import type { TendermintUnconfirmedTransactionsResponse } from '../../routes/txs/tendermint-unconfirmed-transactions-response.d';
interface TxsProps {
data: TendermintUnconfirmedTransactionsResponse | undefined;
}
export const TxList = ({ data }: TxsProps) => {
if (!data) {
return <div>{t('Awaiting transactions')}</div>;
}
return <div>{JSON.stringify(data, null, ' ')}</div>;
};
@@ -7,7 +7,7 @@ import { toHex } from '../search/detect-search';
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code'; import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
import isNumber from 'lodash/isNumber'; import isNumber from 'lodash/isNumber';
const TRUNCATE_LENGTH = 10; const TRUNCATE_LENGTH = 5;
export const TxsInfiniteListItem = ({ export const TxsInfiniteListItem = ({
hash, hash,
@@ -34,10 +34,10 @@ export const TxsInfiniteListItem = ({
className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10 py-2" className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10 py-2"
> >
<div <div
className="text-sm col-span-10 md:col-span-3 leading-none" className="text-sm col-span-10 xl:col-span-3 leading-none"
data-testid="tx-hash" data-testid="tx-hash"
> >
<span className="md:hidden uppercase text-vega-dark-300"> <span className="xl:hidden uppercase text-vega-dark-300">
ID:&nbsp; ID:&nbsp;
</span> </span>
<TruncatedLink <TruncatedLink
@@ -48,10 +48,10 @@ export const TxsInfiniteListItem = ({
/> />
</div> </div>
<div <div
className="text-sm col-span-10 md:col-span-3 leading-none" className="text-sm col-span-10 xl:col-span-3 leading-none"
data-testid="pub-key" data-testid="pub-key"
> >
<span className="md:hidden uppercase text-vega-dark-300"> <span className="xl:hidden uppercase text-vega-dark-300">
By:&nbsp; By:&nbsp;
</span> </span>
<TruncatedLink <TruncatedLink
@@ -61,14 +61,14 @@ export const TxsInfiniteListItem = ({
endChars={TRUNCATE_LENGTH} endChars={TRUNCATE_LENGTH}
/> />
</div> </div>
<div className="text-sm col-span-5 md:col-span-2 leading-none flex items-center"> <div className="text-sm col-span-5 xl:col-span-2 leading-none flex items-center">
<TxOrderType orderType={type} command={command} /> <TxOrderType orderType={type} command={command} />
</div> </div>
<div <div
className="text-sm col-span-3 md:col-span-1 leading-none flex items-center" className="text-sm col-span-3 xl:col-span-1 leading-none flex items-center"
data-testid="tx-block" data-testid="tx-block"
> >
<span className="md:hidden uppercase text-vega-dark-300"> <span className="xl:hidden uppercase text-vega-dark-300">
Block:&nbsp; Block:&nbsp;
</span> </span>
<TruncatedLink <TruncatedLink
@@ -79,10 +79,10 @@ export const TxsInfiniteListItem = ({
/> />
</div> </div>
<div <div
className="text-sm col-span-2 md:col-span-1 leading-none flex items-center" className="text-sm col-span-2 xl:col-span-1 leading-none flex items-center"
data-testid="tx-success" data-testid="tx-success"
> >
<span className="md:hidden uppercase text-vega-dark-300"> <span className="xl:hidden uppercase text-vega-dark-300">
Success:&nbsp; Success:&nbsp;
</span> </span>
{isNumber(code) ? ( {isNumber(code) ? (
@@ -64,7 +64,9 @@ describe('Txs infinite list', () => {
error={Error('test error!')} error={Error('test error!')}
/> />
); );
expect(screen.getByText('Cannot fetch transaction')).toBeInTheDocument(); expect(
screen.getByText('Cannot fetch transaction: Error: test error!')
).toBeInTheDocument();
}); });
it('item renders data of n length into list of n length', () => { it('item renders data of n length into list of n length', () => {
@@ -30,7 +30,7 @@ const NOOP = () => {};
const Item = ({ index, style, isLoading, error }: ItemProps) => { const Item = ({ index, style, isLoading, error }: ItemProps) => {
let content; let content;
if (error) { if (error) {
content = t(`Cannot fetch transaction`); content = t(`Cannot fetch transaction: ${error}`);
} else if (isLoading) { } else if (isLoading) {
content = <Loader />; content = <Loader />;
} else { } else {
@@ -68,7 +68,7 @@ export const TxsInfiniteList = ({
className, className,
}: TxsInfiniteListProps) => { }: TxsInfiniteListProps) => {
const { screenSize } = useScreenDimensions(); const { screenSize } = useScreenDimensions();
const isStacked = ['xs', 'sm'].includes(screenSize); const isStacked = ['xs', 'sm', 'md', 'lg'].includes(screenSize);
if (!txs) { if (!txs) {
if (!areTxsLoading) { if (!areTxsLoading) {
@@ -95,15 +95,15 @@ export const TxsInfiniteList = ({
return ( return (
<div className={className} data-testid="transactions-list"> <div className={className} data-testid="transactions-list">
<div className="lg:grid grid-cols-10 w-full mb-3 hidden text-vega-dark-300 uppercase"> <div className="xl:grid grid-cols-10 w-full mb-3 hidden text-vega-dark-300 uppercase">
<div className="col-span-3"> <div className="col-span-3">
<span className="hidden xl:inline">{t('Transaction')} &nbsp;</span> <span className="hidden xl:inline">Transaction &nbsp;</span>
<span>ID</span> <span>ID</span>
</div> </div>
<div className="col-span-3">{t('Submitted By')}</div> <div className="col-span-3">Submitted By</div>
<div className="col-span-2">{t('Type')}</div> <div className="col-span-2">Type</div>
<div className="col-span-1">{t('Block')}</div> <div className="col-span-1">Block</div>
<div className="col-span-1">{t('Success')}</div> <div className="col-span-1">Success</div>
</div> </div>
<div data-testid="infinite-scroll-wrapper"> <div data-testid="infinite-scroll-wrapper">
<InfiniteLoader <InfiniteLoader
@@ -1,4 +1,5 @@
import { Routes } from '../../routes/route-names'; import { Routes } from '../../routes/route-names';
import { RenderFetched } from '../render-fetched';
import { TruncatedLink } from '../truncate/truncated-link'; import { TruncatedLink } from '../truncate/truncated-link';
import { TxOrderType } from './tx-order-type'; import { TxOrderType } from './tx-order-type';
import { Table, TableRow, TableCell } from '../table'; import { Table, TableRow, TableCell } from '../table';
@@ -8,7 +9,7 @@ import type { BlockExplorerTransactions } from '../../routes/types/block-explore
import isNumber from 'lodash/isNumber'; import isNumber from 'lodash/isNumber';
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code'; import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
import { getTxsDataUrl } from '../../hooks/use-txs-data'; import { getTxsDataUrl } from '../../hooks/use-txs-data';
import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit'; import { Loader } from '@vegaprotocol/ui-toolkit';
import EmptyList from '../empty-list/empty-list'; import EmptyList from '../empty-list/empty-list';
interface TxsPerBlockProps { interface TxsPerBlockProps {
@@ -26,7 +27,7 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
} = useFetch<BlockExplorerTransactions>(url); } = useFetch<BlockExplorerTransactions>(url);
return ( return (
<AsyncRenderer data={data} error={error} loading={!!loading}> <RenderFetched error={error} loading={loading} className="text-body-large">
{data && data.transactions.length > 0 ? ( {data && data.transactions.length > 0 ? (
<div className="overflow-x-auto whitespace-nowrap mb-28"> <div className="overflow-x-auto whitespace-nowrap mb-28">
<Table> <Table>
@@ -94,6 +95,6 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
label={t('0 transactions')} label={t('0 transactions')}
/> />
)} )}
</AsyncRenderer> </RenderFetched>
); );
}; };
@@ -144,12 +144,12 @@ afterEach(() => {
describe('Block', () => { describe('Block', () => {
it('renders error state if error is present', async () => { it('renders error state if error is present', async () => {
(useFetch as jest.Mock).mockReturnValue({ (useFetch as jest.Mock).mockReturnValue({
state: { data: null, loading: false, error: new Error('asd') }, state: { data: null, loading: false, error: 'asd' },
}); });
render(renderComponent()); render(renderComponent());
expect(screen.getByText(`BLOCK ${blockId}`)).toBeInTheDocument(); expect(screen.getByText(`BLOCK ${blockId}`)).toBeInTheDocument();
expect(screen.getByText('Something went wrong: asd')).toBeInTheDocument(); expect(screen.getByText('Error retrieving data')).toBeInTheDocument();
}); });
it('renders loading state if present', async () => { it('renders loading state if present', async () => {
@@ -1,3 +1,4 @@
import React from 'react';
import { Link, useParams } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import { DATA_SOURCES } from '../../../config'; import { DATA_SOURCES } from '../../../config';
import { getDateTimeFormat } from '@vegaprotocol/utils'; import { getDateTimeFormat } from '@vegaprotocol/utils';
@@ -11,8 +12,9 @@ import {
TableCell, TableCell,
} from '../../../components/table'; } from '../../../components/table';
import { TxsPerBlock } from '../../../components/txs/txs-per-block'; import { TxsPerBlock } from '../../../components/txs/txs-per-block';
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit'; import { Button } from '@vegaprotocol/ui-toolkit';
import { Routes } from '../../route-names'; import { Routes } from '../../route-names';
import { RenderFetched } from '../../../components/render-fetched';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useFetch } from '@vegaprotocol/react-helpers'; import { useFetch } from '@vegaprotocol/react-helpers';
import { NodeLink } from '../../../components/links'; import { NodeLink } from '../../../components/links';
@@ -32,7 +34,7 @@ const Block = () => {
return ( return (
<section> <section>
<RouteTitle data-testid="block-header">{t(`BLOCK ${block}`)}</RouteTitle> <RouteTitle data-testid="block-header">{t(`BLOCK ${block}`)}</RouteTitle>
<AsyncRenderer data={blockData} error={error} loading={!!loading}> <RenderFetched error={error} loading={loading}>
<> <>
<div className="grid grid-cols-2 gap-2 mb-8"> <div className="grid grid-cols-2 gap-2 mb-8">
<Link <Link
@@ -121,7 +123,7 @@ const Block = () => {
</> </>
)} )}
</> </>
</AsyncRenderer> </RenderFetched>
</section> </section>
); );
}; };
+11 -16
View File
@@ -1,7 +1,7 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useFetch } from '@vegaprotocol/react-helpers'; import { useFetch } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../components/route-title'; import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { DATA_SOURCES } from '../../config'; import { DATA_SOURCES } from '../../config';
import type { TendermintGenesisResponse } from './tendermint-genesis-response'; import type { TendermintGenesisResponse } from './tendermint-genesis-response';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
@@ -10,26 +10,21 @@ const Genesis = () => {
useDocumentTitle(['Genesis']); useDocumentTitle(['Genesis']);
const { const {
state: { data, loading, error }, state: { data: genesis, loading },
} = useFetch<TendermintGenesisResponse>( } = useFetch<TendermintGenesisResponse>(
`${DATA_SOURCES.tendermintUrl}/genesis` `${DATA_SOURCES.tendermintUrl}/genesis`
); );
if (!genesis?.result.genesis) {
if (loading) {
return <Loader />;
}
return null;
}
return ( return (
<> <section>
<RouteTitle data-testid="genesis-header">{t('Genesis')}</RouteTitle> <RouteTitle data-testid="genesis-header">{t('Genesis')}</RouteTitle>
<AsyncRenderer <SyntaxHighlighter data={genesis?.result.genesis} />
data={data} </section>
error={error}
loading={!!loading}
loadingMessage={t('Loading genesis information...')}
errorMessage={t('Could not fetch genesis data')}
>
<section>
<SyntaxHighlighter data={data?.result.genesis} />
</section>
</AsyncRenderer>
</>
); );
}; };
@@ -1,6 +1,6 @@
import { proposalsDataProvider } from '@vegaprotocol/proposals'; import { proposalsDataProvider } from '@vegaprotocol/proposals';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { ProposalsTable } from '../../components/proposals/proposals-table'; import { ProposalsTable } from '../../components/proposals/proposals-table';
import { RouteTitle } from '../../components/route-title'; import { RouteTitle } from '../../components/route-title';
@@ -1,5 +1,5 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/react-helpers';
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useState } from 'react'; import { useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
@@ -49,7 +49,6 @@ export const MarketPage = () => {
/> />
<AsyncRenderer <AsyncRenderer
noDataMessage={t('This chain has no markets')} noDataMessage={t('This chain has no markets')}
errorMessage={t('Could not fetch market') + ' ' + marketId}
data={data} data={data}
loading={loading} loading={loading}
error={error} error={error}
@@ -4,7 +4,7 @@ import { marketsProvider } from '@vegaprotocol/market-list';
import { RouteTitle } from '../../components/route-title'; import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/react-helpers';
import { MarketsTable } from '../../components/markets/markets-table'; import { MarketsTable } from '../../components/markets/markets-table';
export const MarketsPage = () => { export const MarketsPage = () => {
@@ -1,5 +1,5 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import { NetworkParametersTable } from './network-parameters'; import { NetworkParametersTable } from './network-parameters';
describe('NetworkParametersTable', () => { describe('NetworkParametersTable', () => {
@@ -13,15 +13,14 @@ import {
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { RouteTitle } from '../../components/route-title'; import { RouteTitle } from '../../components/route-title';
import orderBy from 'lodash/orderBy'; import orderBy from 'lodash/orderBy';
import { useNetworkParamsQuery } from '@vegaprotocol/network-parameters'; import { useNetworkParamsQuery } from '@vegaprotocol/react-helpers';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import { useScrollToLocation } from '../../hooks/scroll-to-location'; import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title'; import { useDocumentTitle } from '../../hooks/use-document-title';
const PERCENTAGE_PARAMS = [ const PERCENTAGE_PARAMS = [
'governance.proposal.asset.requiredMajority', 'governance.proposal.asset.requiredMajority',
'governance.proposal.asset.requiredParticipation', 'governance.proposal.asset.requiredParticipation',
'governance.proposal.updateAsset.requiredParticipation',
'governance.proposal.freeform.requiredMajority', 'governance.proposal.freeform.requiredMajority',
'governance.proposal.freeform.requiredParticipation', 'governance.proposal.freeform.requiredParticipation',
'governance.proposal.market.requiredMajority', 'governance.proposal.market.requiredMajority',
@@ -54,8 +53,6 @@ const BIG_NUMBER_PARAMS = [
'governance.proposal.asset.minProposerBalance', 'governance.proposal.asset.minProposerBalance',
'governance.proposal.market.minProposerBalance', 'governance.proposal.market.minProposerBalance',
'governance.proposal.market.minVoterBalance', 'governance.proposal.market.minVoterBalance',
'governance.proposal.updateAsset.minProposerBalance',
'governance.proposal.updateAsset.minVoterBalance',
]; ];
export const NetworkParameterRow = ({ export const NetworkParameterRow = ({
@@ -1,4 +1,4 @@
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../../components/route-title'; import { RouteTitle } from '../../../components/route-title';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useExplorerOracleSpecsQuery } from '../__generated__/Oracles'; import { useExplorerOracleSpecsQuery } from '../__generated__/Oracles';
@@ -8,7 +8,7 @@ import { useScrollToLocation } from '../../../hooks/scroll-to-location';
import filter from 'recursive-key-filter'; import filter from 'recursive-key-filter';
const Oracles = () => { const Oracles = () => {
const { data, loading, error } = useExplorerOracleSpecsQuery(); const { data, loading } = useExplorerOracleSpecsQuery();
useDocumentTitle(['Oracles']); useDocumentTitle(['Oracles']);
useScrollToLocation(); useScrollToLocation();
@@ -16,40 +16,28 @@ const Oracles = () => {
return ( return (
<section> <section>
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle> <RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
<AsyncRenderer {loading ? <Loader /> : null}
data={data} {data?.oracleSpecsConnection?.edges
loading={loading} ? data.oracleSpecsConnection.edges.map((o) => {
error={error} const id = o?.node.dataSourceSpec.spec.id;
loadingMessage={t('Loading oracle data...')} if (!id) {
errorMessage={t('Oracle data could not be loaded')} return null;
noDataMessage={t('No oracles found')} }
noDataCondition={(data) => return (
!data?.oracleSpecsConnection?.edges || <div id={id} key={id} className="mb-10">
data.oracleSpecsConnection.edges?.length === 0 <OracleDetails
} id={id}
> dataSource={o?.node}
{data?.oracleSpecsConnection?.edges showBroadcasts={false}
? data.oracleSpecsConnection.edges.map((o) => { />
const id = o?.node.dataSourceSpec.spec.id; <details>
if (!id) { <summary className="pointer">JSON</summary>
return null; <SyntaxHighlighter data={filter(o, ['__typename'])} />
} </details>
return ( </div>
<div id={id} key={id} className="mb-10"> );
<OracleDetails })
id={id} : null}
dataSource={o?.node}
showBroadcasts={false}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(o, ['__typename'])} />
</details>
</div>
);
})
: null}
</AsyncRenderer>
</section> </section>
); );
}; };
@@ -1,11 +1,12 @@
import { RouteTitle } from '../../../components/route-title'; import { RouteTitle } from '../../../components/route-title';
import { RenderFetched } from '../../../components/render-fetched';
import { truncateByChars } from '@vegaprotocol/utils'; import { truncateByChars } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useDocumentTitle } from '../../../hooks/use-document-title'; import { useDocumentTitle } from '../../../hooks/use-document-title';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useExplorerOracleSpecByIdQuery } from '../__generated__/Oracles'; import { useExplorerOracleSpecByIdQuery } from '../__generated__/Oracles';
import { OracleDetails } from '../components/oracle'; import { OracleDetails } from '../components/oracle';
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter'; import filter from 'recursive-key-filter';
import { TruncateInline } from '../../../components/truncate/truncate'; import { TruncateInline } from '../../../components/truncate/truncate';
@@ -26,14 +27,7 @@ export const Oracle = () => {
{t(`Oracle `)} {t(`Oracle `)}
<TruncateInline startChars={5} endChars={5} text={id || '1'} /> <TruncateInline startChars={5} endChars={5} text={id || '1'} />
</RouteTitle> </RouteTitle>
<AsyncRenderer <RenderFetched error={error} loading={loading}>
data={data}
error={error}
loading={loading}
noDataCondition={(data) => !data?.oracleSpec}
errorMessage={t('Could not load oracle data')}
loadingMessage={t('Loading oracle data...')}
>
{data?.oracleSpec ? ( {data?.oracleSpec ? (
<div id={id} key={id} className="mb-10"> <div id={id} key={id} className="mb-10">
<OracleDetails <OracleDetails
@@ -50,7 +44,7 @@ export const Oracle = () => {
) : ( ) : (
<span></span> <span></span>
)} )}
</AsyncRenderer> </RenderFetched>
</section> </section>
); );
}; };
@@ -48,13 +48,6 @@ query ExplorerPartyAssets($partyId: ID!) {
} }
stakingSummary { stakingSummary {
currentStakeAvailable currentStakeAvailable
linkings(pagination: { first: 100 }) {
edges {
node {
amount
}
}
}
} }
accountsConnection { accountsConnection {
edges { edges {
@@ -10,7 +10,7 @@ export type ExplorerPartyAssetsQueryVariables = Types.Exact<{
}>; }>;
export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string, linkings: { __typename?: 'StakesConnection', edges?: Array<{ __typename?: 'StakeLinkingEdge', node: { __typename?: 'StakeLinking', amount: string } } | null> | null } }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null } } | null> | null } | null } }> } | null }; export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null } } | null> | null } | null } }> } | null };
export const ExplorerPartyAssetsAccountsFragmentDoc = gql` export const ExplorerPartyAssetsAccountsFragmentDoc = gql`
fragment ExplorerPartyAssetsAccounts on AccountBalance { fragment ExplorerPartyAssetsAccounts on AccountBalance {
@@ -64,13 +64,6 @@ export const ExplorerPartyAssetsDocument = gql`
} }
stakingSummary { stakingSummary {
currentStakeAvailable currentStakeAvailable
linkings(pagination: {first: 100}) {
edges {
node {
amount
}
}
}
} }
accountsConnection { accountsConnection {
edges { edges {
@@ -1,24 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { useParams } from 'react-router-dom';
import { toNonHex } from '../../../../components/search/detect-search';
import { PageHeader } from '../../../../components/page-header';
import { useDocumentTitle } from '../../../../hooks/use-document-title';
import { PartyAccounts } from '../components/party-accounts';
const PartyAccountsByAsset = () => {
const { party } = useParams<{ party: string }>();
useDocumentTitle(['Public keys', party || '-']);
const partyId = toNonHex(party ? party : '');
return (
<section>
<PageHeader title={t('Balances by asset')} />
<PartyAccounts partyId={partyId} />
</section>
);
};
export { PartyAccountsByAsset };
@@ -1,9 +1,32 @@
import { AccountManager } from '@vegaprotocol/accounts'; import { t } from '@vegaprotocol/i18n';
import { useCallback } from 'react'; import get from 'lodash/get';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import AssetBalance from '../../../../components/asset-balance/asset-balance';
import { AssetLink, MarketLink } from '../../../../components/links';
import { Table, TableRow } from '../../../../components/table';
import type * as Schema from '@vegaprotocol/types';
import type { ExplorerPartyAssetsAccountsFragment } from '../__generated__/Party-assets';
const accountTypeString: Record<Schema.AccountType, string> = {
ACCOUNT_TYPE_BOND: t('Bond'),
ACCOUNT_TYPE_EXTERNAL: t('External'),
ACCOUNT_TYPE_FEES_INFRASTRUCTURE: t('Fees (Infrastructure)'),
ACCOUNT_TYPE_FEES_LIQUIDITY: t('Fees (Liquidity)'),
ACCOUNT_TYPE_FEES_MAKER: t('Fees (Maker)'),
ACCOUNT_TYPE_GENERAL: t('General'),
ACCOUNT_TYPE_GLOBAL_INSURANCE: t('Global Insurance Pool'),
ACCOUNT_TYPE_GLOBAL_REWARD: t('Global Reward Pool'),
ACCOUNT_TYPE_INSURANCE: t('Insurance'),
ACCOUNT_TYPE_MARGIN: t('Margin'),
ACCOUNT_TYPE_PENDING_TRANSFERS: t('Pending Transfers'),
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: t('Reward - LP Fees received'),
ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES: t('Reward - Maker fees paid'),
ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES: t('Reward - Maker fees received'),
ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: t('Reward - Market proposers'),
ACCOUNT_TYPE_SETTLEMENT: t('Settlement'),
};
interface PartyAccountsProps { interface PartyAccountsProps {
partyId: string; accounts: ExplorerPartyAssetsAccountsFragment[];
} }
/** /**
@@ -11,22 +34,49 @@ interface PartyAccountsProps {
* probably do with sorting by asset, and then within asset, by type with general * probably do with sorting by asset, and then within asset, by type with general
* appearing first and... tbd * appearing first and... tbd
*/ */
export const PartyAccounts = ({ partyId }: PartyAccountsProps) => { export const PartyAccounts = ({ accounts }: PartyAccountsProps) => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const onClickAsset = useCallback(
(assetId?: string) => {
assetId && openAssetDetailsDialog(assetId);
},
[openAssetDetailsDialog]
);
return ( return (
<div className="block min-h-44 h-60 4 w-full border-red-800 relative"> <Table className="max-w-5xl min-w-fit">
<AccountManager <thead>
partyId={partyId} <TableRow modifier="bordered" className="font-mono">
onClickAsset={onClickAsset} <td>{t('Type')}</td>
isReadOnly={true} <td>{t('Market')}</td>
/> <td className="text-right pr-2">{t('Balance')}</td>
</div> <td>{t('Asset')}</td>
</TableRow>
</thead>
<tbody>
{accounts.map((account) => {
const m = get(account, 'market.tradableInstrument.instrument.name');
return (
<TableRow
key={`pa-${account.asset.id}-${account.type}`}
title={account.asset.name}
id={`${accountTypeString[account.type]} ${m ? ` - ${m}` : ''}`}
>
<td className="text-md">{accountTypeString[account.type]}</td>
<td className="text-md">
{account.market?.id ? (
<MarketLink id={account.market?.id} />
) : (
<p>-</p>
)}
</td>
<td className="text-md text-right pr-2">
<AssetBalance
assetId={account.asset.id}
price={account.balance}
showAssetLink={false}
/>
</td>
<td className="text-md">
<AssetLink assetId={account.asset.id} asDialog={true} />
</td>
</TableRow>
);
})}
</tbody>
</Table>
); );
}; };
@@ -1,63 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { useNavigate } from 'react-router-dom';
import { Routes } from '../../../../routes/route-names';
import { Button, Icon, Loader } from '@vegaprotocol/ui-toolkit';
import { PartyBlock } from './party-block';
import type { AccountFields } from '@vegaprotocol/accounts';
export interface PartyBlockAccountProps {
partyId: string;
accountData: AccountFields[] | null;
accountLoading: boolean;
accountError?: Error;
}
/**
* Displays an overview of a party's assets. This uses existing data
* providers to structure the details by asset, rather than looking at
* it by account. The assumption is that this is a more natural way to
* get an idea of the assets and activity of a party.
*/
export const PartyBlockAccounts = ({
partyId,
accountData,
accountLoading,
accountError,
}: PartyBlockAccountProps) => {
const navigate = useNavigate();
const shouldShowActionButton =
accountData && accountData.length > 0 && !accountLoading && !accountError;
const action = shouldShowActionButton ? (
<Button
size="sm"
onClick={() => navigate(`/${Routes.PARTIES}/${partyId}/assets`)}
>
{t('Show all')}
</Button>
) : null;
return (
<PartyBlock title={t('Assets')} action={action}>
{accountData && accountData.length > 0 ? (
<p>
{accountData.length} {t('assets, including')}{' '}
{accountData
.map((a) => a.asset.symbol)
.slice(0, 3)
.join(', ')}
</p>
) : accountLoading && !accountError ? (
<Loader size="small" />
) : accountData && accountData.length === 0 ? (
<p>{t('No accounts found')}</p>
) : (
<p>
<Icon className="mr-1" name="error" />
<span className="text-sm">{t('Could not load assets')}</span>
</p>
)}
</PartyBlock>
);
};
@@ -1,83 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { useExplorerPartyAssetsQuery } from '../__generated__/Party-assets';
import GovernanceAssetBalance from '../../../../components/asset-balance/governance-asset-balance';
import {
Icon,
KeyValueTable,
KeyValueTableRow,
Loader,
} from '@vegaprotocol/ui-toolkit';
import { PartyBlock } from './party-block';
import BigNumber from 'bignumber.js';
export interface PartyBlockStakeProps {
partyId: string;
accountLoading: boolean;
accountError?: Error;
}
/**
* Displays an overview of a single party's staking balance, importantly maintaining'
* the same height before and after the details are loaded in.
*
* Unlike PartyBlockAccounts there is not action button in the title of this block as
* there is no page for it to link to currently. That's a future task.
*/
export const PartyBlockStake = ({
partyId,
accountLoading,
accountError,
}: PartyBlockStakeProps) => {
const partyRes = useExplorerPartyAssetsQuery({
// Don't cache data for this query, party information can move quite quickly
fetchPolicy: 'network-only',
variables: { partyId: partyId },
skip: !partyId,
});
const p = partyRes.data?.partiesConnection?.edges[0].node;
const linkedLength = p?.stakingSummary?.linkings?.edges?.length;
const linkedStake =
linkedLength && linkedLength > 0
? p?.stakingSummary?.linkings?.edges
?.reduce((total, e) => {
return new BigNumber(total).plus(
new BigNumber(e?.node.amount || 0)
);
}, new BigNumber(0))
.toString()
: '0';
return (
<PartyBlock title={t('Staking')}>
{p?.stakingSummary.currentStakeAvailable ? (
<KeyValueTable>
<KeyValueTableRow noBorder={true}>
<div>{t('Available stake')}</div>
<div>
<GovernanceAssetBalance
price={p.stakingSummary.currentStakeAvailable}
/>
</div>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<div>{t('Active stake')}</div>
<div>
<GovernanceAssetBalance price={linkedStake || '0'} />
</div>
</KeyValueTableRow>
</KeyValueTable>
) : accountLoading && !accountError ? (
<Loader size="small" />
) : !accountError ? (
<p>{t('No staking balance')}</p>
) : (
<p>
<Icon className="mr-1" name="error" />
<span className="text-sm">{t('Could not load stake details')}</span>
</p>
)}
</PartyBlock>
);
};
@@ -1,24 +0,0 @@
import type { ReactNode } from 'react';
export interface PartyBlockProps {
children: ReactNode;
title: string;
action?: ReactNode;
}
export function PartyBlock({ children, title, action }: PartyBlockProps) {
return (
<div className="border-2 min-h-[138px] border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
<div
className="flex flex-col md:flex-row gap-1 justify-between content-start mb-2"
data-testid="page-title"
>
<h3 className="font-semibold text-lg">{title}</h3>
{action ? action : null}
</div>
{children}
</div>
);
}
@@ -1,26 +1,24 @@
import { getNodes } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useScreenDimensions } from '@vegaprotocol/react-helpers'; import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { SubHeading } from '../../../components/sub-heading'; import { SubHeading } from '../../../components/sub-heading';
import { Panel } from '../../../components/panel';
import { toNonHex } from '../../../components/search/detect-search'; import { toNonHex } from '../../../components/search/detect-search';
import { useTxsData } from '../../../hooks/use-txs-data'; import { useTxsData } from '../../../hooks/use-txs-data';
import { TxsInfiniteList } from '../../../components/txs'; import { TxsInfiniteList } from '../../../components/txs';
import { PageHeader } from '../../../components/page-header'; import { PageHeader } from '../../../components/page-header';
import { useExplorerPartyAssetsQuery } from './__generated__/Party-assets';
import type { ExplorerPartyAssetsAccountsFragment } from './__generated__/Party-assets';
import { useDocumentTitle } from '../../../hooks/use-document-title'; import { useDocumentTitle } from '../../../hooks/use-document-title';
import { Icon, Intent, Notification, Splash } from '@vegaprotocol/ui-toolkit'; import GovernanceAssetBalance from '../../../components/asset-balance/governance-asset-balance';
import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts'; import { PartyAccounts } from './components/party-accounts';
import { PartyBlockStake } from './components/party-block-stake';
import { PartyBlockAccounts } from './components/party-block-accounts';
import { isValidPartyId } from './components/party-id-error';
import { useDataProvider } from '@vegaprotocol/data-provider';
const Party = () => { const Party = () => {
const { party } = useParams<{ party: string }>(); const { party } = useParams<{ party: string }>();
useDocumentTitle(['Public keys', party || '-']); useDocumentTitle(['Public keys', party || '-']);
const navigate = useNavigate();
const partyId = toNonHex(party ? party : ''); const partyId = toNonHex(party ? party : '');
const { isMobile } = useScreenDimensions(); const { isMobile } = useScreenDimensions();
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]); const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
@@ -30,72 +28,71 @@ const Party = () => {
filters, filters,
}); });
const variables = useMemo(() => ({ partyId }), [partyId]); const partyRes = useExplorerPartyAssetsQuery({
const { // Don't cache data for this query, party information can move quite quickly
data: AccountData, fetchPolicy: 'network-only',
loading: AccountLoading, variables: { partyId: partyId },
error: AccountError, skip: !party,
} = useDataProvider({
dataProvider: aggregatedAccountsDataProvider,
variables,
}); });
if (!isValidPartyId(partyId)) { const p = partyRes.data?.partiesConnection?.edges[0].node;
return (
<div className="max-w-sm mx-auto"> const header = p?.id ? (
<Notification <PageHeader
message={t('Invalid party ID')} title={p.id}
intent={Intent.Danger} copy
buttonProps={{ truncateStart={visibleChars}
text: t('Go back'), truncateEnd={visibleChars}
action: () => navigate(-1), />
className: 'py-1', ) : (
size: 'sm', <Panel>
}} <p>No data found for public key {party}</p>
/> </Panel>
</div> );
);
} const staking = (
<section>
{p?.stakingSummary?.currentStakeAvailable ? (
<div className="mt-4 leading-3">
<strong className="font-semibold">{t('Staking Balance: ')}</strong>
<GovernanceAssetBalance
price={p.stakingSummary.currentStakeAvailable}
/>
</div>
) : null}
</section>
);
const accounts = getNodes<ExplorerPartyAssetsAccountsFragment>(
p?.accountsConnection
);
return ( return (
<section> <section>
<PageHeader <h1
title={partyId} className="font-alpha calt uppercase font-xl mb-4 text-vega-dark-100 dark:text-vega-light-100"
copy data-testid="parties-header"
truncateStart={visibleChars} >
truncateEnd={visibleChars} {t('Public key')}
/> </h1>
{partyRes.data ? (
<>
{header}
<SubHeading>{t('Asset data')}</SubHeading>
{accounts ? <PartyAccounts accounts={accounts} /> : null}
{staking}
<div className="grid md:grid-flow-col grid-flow-row md:space-x-4 grid-cols-1 md:grid-cols-2 w-full"> <SubHeading>{t('Transactions')}</SubHeading>
<PartyBlockAccounts <TxsInfiniteList
accountError={AccountError} hasMoreTxs={hasMoreTxs}
accountLoading={AccountLoading} areTxsLoading={loading}
accountData={AccountData} txs={txsData}
partyId={partyId} loadMoreTxs={loadTxs}
/> error={error}
<PartyBlockStake className="mb-28"
accountError={AccountError} />
accountLoading={AccountLoading} </>
partyId={partyId} ) : null}
/>
</div>
<SubHeading>{t('Transactions')}</SubHeading>
{!error && txsData ? (
<TxsInfiniteList
hasMoreTxs={hasMoreTxs}
areTxsLoading={loading}
txs={txsData}
loadMoreTxs={loadTxs}
error={error}
className="mb-28"
/>
) : (
<Splash>
<Icon name="error" className="mr-1" />
&nbsp;{t('Could not load transaction list for party')}
</Splash>
)}
</section> </section>
); );
}; };
@@ -1,7 +0,0 @@
import { Outlet } from 'react-router-dom';
const PartiesSubPage = () => {
return <Outlet />;
};
export default PartiesSubPage;
@@ -0,0 +1,35 @@
import React from 'react';
import { DATA_SOURCES } from '../../config';
import type { TendermintUnconfirmedTransactionsResponse } from '../txs/tendermint-unconfirmed-transactions-response.d';
import { TxList } from '../../components/txs';
import { RouteTitle } from '../../components/route-title';
import { t } from '@vegaprotocol/i18n';
import { useFetch } from '@vegaprotocol/react-helpers';
import { useDocumentTitle } from '../../hooks/use-document-title';
const PendingTxs = () => {
const {
state: { data: unconfirmedTransactions },
} = useFetch<TendermintUnconfirmedTransactionsResponse>(
`${DATA_SOURCES.tendermintUrl}/unconfirmed_txs`
);
useDocumentTitle(['Pending transactions']);
return (
<section>
<RouteTitle data-testid="unconfirmed-transactions-header">
{t('Unconfirmed transactions')}
</RouteTitle>
<br />
<div>{t(`Number: ${unconfirmedTransactions?.result?.n_txs || 0}`)}</div>
<br />
<div>
<br />
<TxList data={unconfirmedTransactions} />
</div>
</section>
);
};
export { PendingTxs };
+20 -38
View File
@@ -14,6 +14,7 @@ import { Block } from './blocks/id';
import { Blocks } from './blocks/home'; import { Blocks } from './blocks/home';
import { Tx } from './txs/id'; import { Tx } from './txs/id';
import { TxsList } from './txs/home'; import { TxsList } from './txs/home';
import { PendingTxs } from './pending';
import flags from '../config/flags'; import flags from '../config/flags';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { Routes } from './route-names'; import { Routes } from './route-names';
@@ -28,7 +29,6 @@ import compact from 'lodash/compact';
import { AssetLink, MarketLink } from '../components/links'; import { AssetLink, MarketLink } from '../components/links';
import { truncateMiddle } from '@vegaprotocol/ui-toolkit'; import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
import { remove0x } from '@vegaprotocol/utils'; import { remove0x } from '@vegaprotocol/utils';
import { PartyAccountsByAsset } from './parties/id/accounts';
export type Navigable = { export type Navigable = {
path: string; path: string;
@@ -75,43 +75,14 @@ const partiesRoutes: Route[] = flags.parties
}, },
{ {
path: ':party', path: ':party',
element: <Party />, element: <PartySingle />,
handle: {
children: [ breadcrumb: (params: Params<string>) => (
{ <Link to={linkTo(Routes.PARTIES, params.party)}>
index: true, {truncateMiddle(params.party as string)}
element: <PartySingle />, </Link>
handle: { ),
breadcrumb: (params: Params<string>) => ( },
<Link to={linkTo(Routes.PARTIES, params.party)}>
{truncateMiddle(params.party as string)}
</Link>
),
},
},
{
path: 'assets',
element: <Party />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
{truncateMiddle(params.party as string)}
</Link>
),
},
children: [
{
index: true,
element: <PartyAccountsByAsset />,
handle: {
breadcrumb: () => {
return t('Assets');
},
},
},
],
},
],
}, },
], ],
}, },
@@ -266,6 +237,17 @@ export const routerConfig: Route[] = [
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>, breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
}, },
children: [ children: [
{
path: 'pending',
element: <PendingTxs />,
handle: {
breadcrumb: () => (
<Link to={linkTo(Routes.TX, 'pending')}>
{t('Pending transactions')}
</Link>
),
},
},
{ {
path: ':txHash', path: ':txHash',
element: <Tx />, element: <Tx />,
@@ -10,7 +10,7 @@ interface TxDetailsProps {
export const txDetailsTruncateLength = 30; export const txDetailsTruncateLength = 30;
export const TxDetails = ({ txData, pubKey }: TxDetailsProps) => { export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
if (!txData) { if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>; return <>{t('Awaiting Block Explorer transaction details')}</>;
} }
@@ -28,7 +28,6 @@ import {
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import classNames from 'classnames'; import classNames from 'classnames';
import { NodeStatus, NodeStatusMapping } from '@vegaprotocol/types'; import { NodeStatus, NodeStatusMapping } from '@vegaprotocol/types';
import { PartyLink } from '../../components/links';
type RateProps = { type RateProps = {
value: BigNumber | number | undefined; value: BigNumber | number | undefined;
@@ -192,9 +191,7 @@ export const ValidatorsPage = () => {
<KeyValueTable> <KeyValueTable>
<KeyValueTableRow> <KeyValueTableRow>
<div>{t('ID')}</div> <div>{t('ID')}</div>
<div className="break-all text-xs font-mono"> <div className="break-all text-xs">{v.id}</div>
{v.id}
</div>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<div>{t('Status')}</div> <div>{t('Status')}</div>
@@ -221,14 +218,12 @@ export const ValidatorsPage = () => {
</div> </div>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<div>{t('Key')}</div> <div>{t('Public key')}</div>
<div className="break-all text-xs"> <div className="break-all text-xs">{v.pubkey}</div>
<PartyLink id={v.pubkey} />
</div>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<div>{t('Ethereum address')}</div> <div>{t('Ethereum address')}</div>
<div className="break-all text-xs font-mono"> <div className="break-all text-xs">
<EtherscanLink address={v.ethereumAddress} />{' '} <EtherscanLink address={v.ethereumAddress} />{' '}
<CopyWithTooltip text={v.ethereumAddress}> <CopyWithTooltip text={v.ethereumAddress}>
<button title={t('Copy address to clipboard')}> <button title={t('Copy address to clipboard')}>
@@ -239,9 +234,7 @@ export const ValidatorsPage = () => {
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
<div>{t('Tendermint public key')}</div> <div>{t('Tendermint public key')}</div>
<div className="break-all text-xs font-mono"> <div className="break-all text-xs">{v.tmPubkey}</div>
{v.tmPubkey}
</div>
</KeyValueTableRow> </KeyValueTableRow>
<KeyValueTableRow> <KeyValueTableRow>
-5
View File
@@ -32,11 +32,6 @@
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px); line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
} }
.vega-ag-grid .ag-row {
border-width: 1px 0;
border-bottom: 1px solid transparent;
}
/* Light variables */ /* Light variables */
.ag-theme-balham { .ag-theme-balham {
--ag-background-color: theme(colors.white); --ag-background-color: theme(colors.white);
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables # App configuration variables
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_VEGA_URL=https://api.vega.community/graphql NX_VEGA_URL=https://api.vega.xyz/query
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
@@ -4,11 +4,12 @@ import {
navigation, navigation,
} from '../../support/common.functions'; } from '../../support/common.functions';
import { import {
convertUnixTimestampToDateformat,
createRawProposal, createRawProposal,
createTenDigitUnixTimeStampForSpecifiedDays, createTenDigitUnixTimeStampForSpecifiedDays,
enterUniqueFreeFormProposalBody, enterUniqueFreeFormProposalBody,
generateFreeFormProposalTitle, generateFreeFormProposalTitle,
getDateFormatForSpecifiedDays, getGovernanceProposalDateFormatForSpecifiedDays,
getProposalIdFromList, getProposalIdFromList,
getProposalInformationFromTable, getProposalInformationFromTable,
getSubmittedProposalFromProposalList, getSubmittedProposalFromProposalList,
@@ -22,7 +23,6 @@ import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../../../governan
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../../../governance-e2e/src/support/wallet-teardown.functions'; import { vegaWalletSetSpecifiedApprovalAmount } from '../../../../governance-e2e/src/support/wallet-teardown.functions';
import type { testFreeformProposal } from '../../support/common-interfaces'; import type { testFreeformProposal } from '../../support/common-interfaces';
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
const proposalVoteProgressForPercentage = const proposalVoteProgressForPercentage =
'[data-testid="vote-progress-indicator-percentage-for"]'; '[data-testid="vote-progress-indicator-percentage-for"]';
@@ -99,22 +99,20 @@ describe(
getSubmittedProposalFromProposalList(proposalTitle).within(() => getSubmittedProposalFromProposalList(proposalTitle).within(() =>
cy.get(viewProposalButton).click() cy.get(viewProposalButton).click()
); );
cy.wrap( convertUnixTimestampToDateformat(proposalTimeStamp).then(
formatDateWithLocalTimezone(new Date(proposalTimeStamp * 1000)) (closingDate) => {
).then((closingDate) => { getProposalInformationFromTable('Closes on')
getProposalInformationFromTable('Closes on') .contains(closingDate)
.contains(closingDate) .should('be.visible');
.should('be.visible'); }
}); );
cy.wrap( getGovernanceProposalDateFormatForSpecifiedDays(0).then(
formatDateWithLocalTimezone( (proposalDate) => {
new Date(createTenDigitUnixTimeStampForSpecifiedDays(0) * 1000) getProposalInformationFromTable('Proposed on')
) .contains(proposalDate)
).then((proposalDate) => { .should('be.visible');
getProposalInformationFromTable('Proposed on') }
.contains(proposalDate) );
.should('be.visible');
});
}); });
it('Newly created proposal details - shows default status set to fail', function () { it('Newly created proposal details - shows default status set to fail', function () {
@@ -157,16 +155,18 @@ describe(
cy.getByTestId('vote-buttons').contains('against').should('be.visible'); cy.getByTestId('vote-buttons').contains('against').should('be.visible');
cy.getByTestId('vote-buttons').contains('for').should('be.visible'); cy.getByTestId('vote-buttons').contains('for').should('be.visible');
voteForProposal('for'); voteForProposal('for');
getDateFormatForSpecifiedDays(0).then((votedDate) => { getGovernanceProposalDateFormatForSpecifiedDays(0, 'shortMonth').then(
// 3001-VOTE-051 (votedDate) => {
// 3001-VOTE-093 // 3001-VOTE-051
cy.contains('You voted:') // 3001-VOTE-093
.siblings() cy.contains('You voted:')
.contains('For') .siblings()
.siblings() .contains('For')
.contains(votedDate) .siblings()
.should('be.visible'); .contains(votedDate)
}); .should('be.visible');
}
);
cy.get(proposalVoteProgressForPercentage) // 3001-VOTE-072 cy.get(proposalVoteProgressForPercentage) // 3001-VOTE-072
.contains('100.00%') .contains('100.00%')
.and('be.visible'); .and('be.visible');
@@ -29,8 +29,6 @@ const tableReceiverAddress = '[col-id="details.receiverAddress"]';
const tableWithdrawnTimeStamp = '[col-id="withdrawnTimestamp"]'; const tableWithdrawnTimeStamp = '[col-id="withdrawnTimestamp"]';
const tableWithdrawnStatus = '[col-id="status"]'; const tableWithdrawnStatus = '[col-id="status"]';
const tableCreatedTimeStamp = '[col-id="createdTimestamp"]'; const tableCreatedTimeStamp = '[col-id="createdTimestamp"]';
const toastContent = 'toast-content';
const toastPanel = 'toast-panel';
const usdtName = 'USDC (local)'; const usdtName = 'USDC (local)';
const usdcEthAddress = '0x1b8a1B6CBE5c93609b46D1829Cc7f3Cb8eeE23a0'; const usdcEthAddress = '0x1b8a1B6CBE5c93609b46D1829Cc7f3Cb8eeE23a0';
const usdcSymbol = 'tUSDC'; const usdcSymbol = 'tUSDC';
@@ -151,6 +149,7 @@ context(
}); });
}); });
// Skipping because of bug #1857
it('Able to withdraw asset: -eth wallet not connected', function () { it('Able to withdraw asset: -eth wallet not connected', function () {
const ethWalletAddress = Cypress.env('ethWalletPublicKey'); const ethWalletAddress = Cypress.env('ethWalletPublicKey');
cy.reload(); cy.reload();
@@ -188,26 +187,8 @@ context(
.should('have.attr', 'href') .should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/address/'); .and('contain', 'https://sepolia.etherscan.io/address/');
cy.get(tableCreatedTimeStamp).should('not.be.empty'); cy.get(tableCreatedTimeStamp).should('not.be.empty');
}); cy.getByTestId(completeWithdrawalButton).click();
ethereumWalletConnect(); // Unable to complete withdrawal in Capsule
cy.getByTestId(completeWithdrawalButton).click();
cy.getByTestId(toastContent)
.last()
.should('contain.text', 'Awaiting confirmation')
.within(() => {
cy.getByTestId('external-link').should('exist');
});
cy.getByTestId(toastContent)
.first()
.should('contain.text', 'The withdrawal has been approved.')
.within(() => {
cy.getByTestId(toastPanel).should('contain.text', '110.00', 'tUSDC');
});
cy.getByTestId(toastContent)
.last()
.should('contain.text', 'Transaction confirmed')
.within(() => {
cy.getByTestId('external-link').should('exist');
}); });
}); });
@@ -237,10 +218,7 @@ context(
}); });
function waitForAssetsDisplayed(expectedAsset: string) { function waitForAssetsDisplayed(expectedAsset: string) {
cy.getByTestId('currency-title', txTimeout).should( cy.getByTestId('currency-title').should('contain.text', expectedAsset);
'contain.text',
expectedAsset
);
} }
} }
); );
@@ -27,7 +27,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
cy.getByTestId('app-announcement').should('not.exist'); cy.getByTestId('app-announcement').should('not.exist');
}); });
it('should show open or enacted proposals without proposal summary', function () { it('should show open or enacted proposals with proposal summary', function () {
cy.get('body').then(($body) => { cy.get('body').then(($body) => {
if (!$body.find('[data-testid="proposals-list-item"]').length) { if (!$body.find('[data-testid="proposals-list-item"]').length) {
cy.createMarket(); cy.createMarket();
@@ -43,6 +43,12 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
.invoke('text') .invoke('text')
.should('not.be.empty'); .should('not.be.empty');
cy.getByTestId('proposal-type').invoke('text').should('not.be.empty'); cy.getByTestId('proposal-type').invoke('text').should('not.be.empty');
cy.getByTestId('proposal-description')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-details')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-status') cy.getByTestId('proposal-status')
.invoke('text') .invoke('text')
.should('not.be.empty'); .should('not.be.empty');
@@ -101,15 +101,15 @@ context(
); );
cy.getByTestId('protocol-upgrade-proposal-release-tag').should( cy.getByTestId('protocol-upgrade-proposal-release-tag').should(
'have.text', 'have.text',
'Vega release tag: v1' 'Vega release tagv1'
); );
cy.getByTestId('protocol-upgrade-proposal-block-height').should( cy.getByTestId('protocol-upgrade-proposal-block-height').should(
'have.text', 'have.text',
'Upgrade block height: 2015942' 'Upgrade block height2015942'
); );
cy.getByTestId('protocol-upgrade-proposal-status').should( cy.getByTestId('protocol-upgrade-proposal-status').should(
'have.text', 'have.text',
'Approved ' 'Approved'
); );
}); });
}); });
@@ -1,4 +1,3 @@
import { format } from 'date-fns';
import { closeDialog, navigateTo, navigation } from './common.functions'; import { closeDialog, navigateTo, navigation } from './common.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from './staking.functions'; import { ensureSpecifiedUnstakedTokensAreAssociated } from './staking.functions';
@@ -16,6 +15,35 @@ const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const epochTimeout = Cypress.env('epochTimeout'); const epochTimeout = Cypress.env('epochTimeout');
const proposalTimeout = { timeout: 14000 }; const proposalTimeout = { timeout: 14000 };
export function convertUnixTimestampToDateformat(
unixTimestamp: number,
monthTextLength = 'longMonth'
) {
const dateSupplied = new Date(unixTimestamp * 1000);
const year = dateSupplied.getFullYear();
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
const month = months[dateSupplied.getMonth()];
const shortMonth = months[dateSupplied.getMonth()].substring(0, 3),
date = dateSupplied.getDate();
if (monthTextLength === 'longMonth') {
return cy.wrap(`${date} ${month} ${year}`);
} else return cy.wrap(`${date} ${shortMonth} ${year}`);
}
export function createTenDigitUnixTimeStampForSpecifiedDays( export function createTenDigitUnixTimeStampForSpecifiedDays(
durationDays: number durationDays: number
) { ) {
@@ -24,13 +52,6 @@ export function createTenDigitUnixTimeStampForSpecifiedDays(
return (timestamp = Math.floor(timestamp / 1000)); return (timestamp = Math.floor(timestamp / 1000));
} }
export function getDateFormatForSpecifiedDays(days: number) {
const date = new Date(
createTenDigitUnixTimeStampForSpecifiedDays(days) * 1000
);
return cy.wrap(format(date, 'dd MMM yyyy'));
}
export function enterRawProposalBody(timestamp: number) { export function enterRawProposalBody(timestamp: number) {
cy.fixture('/proposals/raw.json').then((rawProposal) => { cy.fixture('/proposals/raw.json').then((rawProposal) => {
rawProposal.terms.closingTimestamp = timestamp; rawProposal.terms.closingTimestamp = timestamp;
@@ -83,6 +104,16 @@ export function getProposalIdFromList(proposalTitle: string) {
}); });
} }
export function getGovernanceProposalDateFormatForSpecifiedDays(
days: number,
shortOrLong?: string
) {
return convertUnixTimestampToDateformat(
createTenDigitUnixTimeStampForSpecifiedDays(days),
shortOrLong
);
}
export function getProposalInformationFromTable(heading: string) { export function getProposalInformationFromTable(heading: string) {
return cy.get(proposalInformationTableRows).contains(heading).siblings(); return cy.get(proposalInformationTableRows).contains(heading).siblings();
} }
+1 -1
View File
@@ -1,7 +1,7 @@
# App configuration variables # App configuration variables
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}' NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
-5
View File
@@ -1,5 +0,0 @@
function ReactMarkdown({ children }) {
return <div>{children}</div>;
}
export default ReactMarkdown;
+1 -11
View File
@@ -20,17 +20,12 @@ import type { EthereumConfig } from '@vegaprotocol/web3';
import { import {
createConnectors, createConnectors,
useEthTransactionManager, useEthTransactionManager,
useEthTransactionUpdater,
useEthWithdrawApprovalsManager, useEthWithdrawApprovalsManager,
useWeb3ConnectStore, useWeb3ConnectStore,
} from '@vegaprotocol/web3'; } from '@vegaprotocol/web3';
import { Web3Provider } from '@vegaprotocol/web3'; import { Web3Provider } from '@vegaprotocol/web3';
import { VegaWalletDialogs } from './components/vega-wallet-dialogs'; import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
import { import { VegaWalletProvider } from '@vegaprotocol/wallet';
useVegaTransactionManager,
useVegaTransactionUpdater,
VegaWalletProvider,
} from '@vegaprotocol/wallet';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useEthereumConfig } from '@vegaprotocol/web3'; import { useEthereumConfig } from '@vegaprotocol/web3';
import { import {
@@ -42,7 +37,6 @@ import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client'; import type { InMemoryCacheConfig } from '@apollo/client';
import { WithdrawalDialog } from '@vegaprotocol/withdraws'; import { WithdrawalDialog } from '@vegaprotocol/withdraws';
import { SplashLoader } from './components/splash-loader'; import { SplashLoader } from './components/splash-loader';
import { ToastsManager } from './toasts-manager';
const cache: InMemoryCacheConfig = { const cache: InMemoryCacheConfig = {
typePolicies: { typePolicies: {
@@ -87,10 +81,7 @@ const Web3Container = ({
providerUrl: string; providerUrl: string;
}) => { }) => {
const InitializeHandlers = () => { const InitializeHandlers = () => {
useVegaTransactionManager();
useVegaTransactionUpdater();
useEthTransactionManager(); useEthTransactionManager();
useEthTransactionUpdater();
useEthWithdrawApprovalsManager(); useEthWithdrawApprovalsManager();
return null; return null;
}; };
@@ -144,7 +135,6 @@ const Web3Container = ({
<NetworkInfo /> <NetworkInfo />
</footer> </footer>
</AppLayout> </AppLayout>
<ToastsManager />
<InitializeHandlers /> <InitializeHandlers />
<VegaWalletDialogs /> <VegaWalletDialogs />
<TransactionModal /> <TransactionModal />
@@ -23,7 +23,6 @@ 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 break-words', { className={classNames('font-alpha calt text-5xl', {
'mt-0': !marginTop, 'mt-0': !marginTop,
'mb-0': !marginBottom, 'mb-0': !marginBottom,
})} })}
@@ -21,10 +21,10 @@ export const useGetUserBalances = (account: string | undefined) => {
token.allowance(account, config.staking_bridge_contract.address), token.allowance(account, config.staking_bridge_contract.address),
]); ]);
const balance = toBigNum(b.toString(), decimals); const balance = toBigNum(b, decimals);
const walletBalance = toBigNum(w.toString(), decimals); const walletBalance = toBigNum(w, decimals);
const lien = toBigNum(stats.lien.toString(), decimals); const lien = toBigNum(stats.lien, decimals);
const allowance = toBigNum(a.toString(), decimals); const allowance = toBigNum(a, decimals);
return { return {
balanceFormatted: balance, balanceFormatted: balance,
@@ -21,14 +21,8 @@ export function useRefreshAssociatedBalances() {
]); ]);
updateBalances({ updateBalances({
walletAssociatedBalance: toBigNum( walletAssociatedBalance: toBigNum(walletAssociatedBalance, decimals),
walletAssociatedBalance.toString(), vestingAssociatedBalance: toBigNum(vestingAssociatedBalance, decimals),
decimals
),
vestingAssociatedBalance: toBigNum(
vestingAssociatedBalance.toString(),
decimals
),
}); });
}, },
[staking, vesting, updateBalances, decimals] [staking, vesting, updateBalances, decimals]
@@ -31,18 +31,12 @@ export const useRefreshBalances = (address: string) => {
pubKey ? vesting.stake_balance(address, pubKey) : null, pubKey ? vesting.stake_balance(address, pubKey) : null,
]); ]);
const balance = toBigNum(b.toString(), decimals); const balance = toBigNum(b, decimals);
const walletBalance = toBigNum(w.toString(), decimals); const walletBalance = toBigNum(w, decimals);
const lien = toBigNum(stats.lien.toString(), decimals); const lien = toBigNum(stats.lien, decimals);
const allowance = toBigNum(a.toString(), decimals); const allowance = toBigNum(a, decimals);
const walletAssociatedBalance = toBigNum( const walletAssociatedBalance = toBigNum(walletStakeBalance, decimals);
walletStakeBalance ? walletStakeBalance.toString() : 0, const vestingAssociatedBalance = toBigNum(vestingStakeBalance, decimals);
decimals
);
const vestingAssociatedBalance = toBigNum(
vestingStakeBalance ? vestingStakeBalance.toString() : 0,
decimals
);
updateBalances({ updateBalances({
balanceFormatted: balance, balanceFormatted: balance,
+3 -11
View File
@@ -201,7 +201,7 @@
"NewFreeform": "Freeform", "NewFreeform": "Freeform",
"tokenVotes": "Token votes", "tokenVotes": "Token votes",
"liquidityVotes": "Liquidity votes", "liquidityVotes": "Liquidity votes",
"castYourVote": "Cast your vote", "yourVote": "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 tokens voted", "totalTokensVotes": "Total yes tokens",
"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,10 +631,7 @@
"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",
@@ -680,7 +677,6 @@
"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",
@@ -698,7 +694,6 @@
"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",
@@ -742,7 +737,6 @@
"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",
@@ -797,7 +791,5 @@
"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,11 +1,7 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon } from '@vegaprotocol/ui-toolkit'; import * as Schema from '@vegaprotocol/types';
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,
@@ -13,63 +9,19 @@ export const CurrentProposalState = ({
proposal: ProposalFieldsFragment | ProposalQuery['proposal']; proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
let proposalStatus: ReactNode; let className = 'text-white';
let variant = 'tertiary' as ProposalInfoLabelVariant;
switch (proposal?.state) { if (
case ProposalState.STATE_ENACTED: { proposal?.state === Schema.ProposalState.STATE_DECLINED ||
proposalStatus = ( proposal?.state === Schema.ProposalState.STATE_FAILED ||
<> proposal?.state === Schema.ProposalState.STATE_REJECTED
<span className="mr-2">{t('voteState_Enacted')}</span> ) {
<Icon name={'tick'} /> className = 'text-danger';
</> } else if (
); proposal?.state === Schema.ProposalState.STATE_ENACTED ||
break; proposal?.state === Schema.ProposalState.STATE_PASSED
} ) {
case ProposalState.STATE_PASSED: { className = 'text-white';
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>
);
}; };
@@ -2,8 +2,8 @@ import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing'; import { MockedProvider } from '@apollo/client/testing';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types'; import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/react-helpers';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { generateProposal } from '../../test-helpers/generate-proposals'; import { generateProposal } from '../../test-helpers/generate-proposals';
import { CurrentProposalStatus } from './current-proposal-status'; import { CurrentProposalStatus } from './current-proposal-status';
@@ -16,12 +16,17 @@ 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(
@@ -30,12 +35,17 @@ 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', () => {
@@ -43,12 +53,17 @@ 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(
@@ -89,6 +104,7 @@ 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,6 +6,7 @@ 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';
@@ -19,12 +20,16 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
const terms = proposal?.terms; const terms = proposal?.terms;
return ( return (
<RoundedWrapper paddingBottom={true}> <RoundedWrapper>
<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')
@@ -45,24 +50,26 @@ 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 noBorder={!proposal?.errorDetails}> <KeyValueTableRow>
{t('rejectionReason')} {t('rejectionReason')}
{proposal.rejectionReason} {proposal.rejectionReason}
</KeyValueTableRow> </KeyValueTableRow>
) : null} ) : null}
{proposal?.errorDetails ? ( {proposal?.errorDetails ? (
<KeyValueTableRow noBorder={true}> <KeyValueTableRow>
{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,68 +1,68 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { import { generateProposal } from '../../test-helpers/generate-proposals';
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'], <ProposalHeader proposal={proposal} />
isListItem = true );
) => render(<ProposalHeader proposal={proposal} isListItem={isListItem} />);
describe('Proposal header', () => { describe('Proposal header', () => {
it('Renders New market proposal', () => { it('Renders New market proposal', () => {
renderComponent( render(
generateProposal({ renderComponent(
rationale: { generateProposal({
title: 'New some market', rationale: {
description: 'A new some market', title: 'New some market',
}, description: 'A new some market',
terms: { },
change: { terms: {
__typename: 'NewMarket', change: {
instrument: { __typename: 'NewMarket',
__typename: 'InstrumentConfiguration', instrument: {
name: 'Some market', __typename: 'InstrumentConfiguration',
code: 'FX:BTCUSD/DEC99', name: 'Some market',
futureProduct: { code: 'FX:BTCUSD/DEC99',
__typename: 'FutureProduct', futureProduct: {
settlementAsset: { __typename: 'FutureProduct',
__typename: 'Asset', settlementAsset: {
symbol: 'tGBP', __typename: 'Asset',
symbol: 'tGBP',
},
}, },
}, },
}, },
}, },
}, })
}) )
); );
expect(screen.getByTestId('proposal-title')).toHaveTextContent( expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'New some market' 'New some market'
); );
expect(screen.getByTestId('proposal-type')).toHaveTextContent('New market'); expect(screen.getByTestId('proposal-type')).toHaveTextContent('New market');
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
'A new some market'
);
expect(screen.getByTestId('proposal-details')).toHaveTextContent( expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'tGBP settled future.' 'tGBP settled future.'
); );
}); });
it('Renders Update market proposal', () => { it('Renders Update market proposal', () => {
renderComponent( render(
generateProposal({ renderComponent(
rationale: { generateProposal({
title: 'New market id', rationale: {
}, 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'
@@ -79,49 +79,53 @@ describe('Proposal header', () => {
}); });
it('Renders New asset proposal - ERC20', () => { it('Renders New asset proposal - ERC20', () => {
renderComponent( render(
generateProposal({ renderComponent(
rationale: { generateProposal({
title: 'New asset: Fake currency', rationale: {
description: '', title: 'New asset: Fake currency',
}, description: '',
terms: { },
change: { terms: {
__typename: 'NewAsset', change: {
name: 'Fake currency', __typename: 'NewAsset',
symbol: 'FAKE', name: 'Fake currency',
source: { symbol: 'FAKE',
__typename: 'ERC20', source: {
contractAddress: '0x0', __typename: 'ERC20',
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 contract address: 0x0' 'Symbol: FAKE. ERC20 0x0'
); );
}); });
it('Renders New asset proposal - BuiltInAsset', () => { it('Renders New asset proposal - BuiltInAsset', () => {
renderComponent( render(
generateProposal({ renderComponent(
terms: { generateProposal({
change: { terms: {
__typename: 'NewAsset', change: {
name: 'Fake currency', __typename: 'NewAsset',
symbol: 'BIA', name: 'Fake currency',
source: { symbol: 'BIA',
__typename: 'BuiltinAsset', source: {
maxFaucetAmountMint: '300', __typename: 'BuiltinAsset',
maxFaucetAmountMint: '300',
},
}, },
}, },
}, })
}) )
); );
expect(screen.getByTestId('proposal-title')).toHaveTextContent( expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Unknown proposal' 'Unknown proposal'
@@ -133,22 +137,24 @@ describe('Proposal header', () => {
}); });
it('Renders Update network', () => { it('Renders Update network', () => {
renderComponent( render(
generateProposal({ renderComponent(
rationale: { generateProposal({
title: 'Network parameter', rationale: {
}, title: 'Network parameter',
terms: { },
change: { terms: {
__typename: 'UpdateNetworkParameter', change: {
networkParameter: { __typename: 'UpdateNetworkParameter',
__typename: 'NetworkParameter', networkParameter: {
key: 'Network key', __typename: 'NetworkParameter',
value: 'Network value', key: 'Network key',
value: 'Network value',
},
}, },
}, },
}, })
}) )
); );
expect(screen.getByTestId('proposal-title')).toHaveTextContent( expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Network parameter' 'Network parameter'
@@ -161,211 +167,122 @@ describe('Proposal header', () => {
); );
}); });
it('Renders Freeform proposal - short rationale', () => { it('Renders Freeform network - short rationale', () => {
renderComponent( render(
generateProposal({ renderComponent(
id: 'short', generateProposal({
rationale: { id: 'short',
title: '0x0', rationale: {
}, 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)', () => {
renderComponent( render(
generateProposal({ renderComponent(
id: 'long', generateProposal({
rationale: { id: 'long',
title: '0x0', rationale: {
description: title: '0x0',
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.', description:
}, 'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
terms: {
change: {
__typename: 'NewFreeform',
}, },
}, terms: {
}) change: {
__typename: 'NewFreeform',
},
},
})
)
); );
// For a rationale over 100 chars, we expect the header to be truncated at
// 100 chars with ellipsis and the details-one element to contain the rest.
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');
// Rationale in list view is not rendered
expect(
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
});
it('Renders Freeform proposal - long rationale (105 chars) - details', () => {
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',
},
},
}),
false
);
expect(screen.getByTestId('proposal-description')).toHaveTextContent( expect(screen.getByTestId('proposal-description')).toHaveTextContent(
/Class aptent/ 'Class aptent taciti sociosqu ad litora torquent per conubia'
); );
expect(screen.getByTestId('proposal-details')).toHaveTextContent('long');
}); });
// 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', () => {
renderComponent( render(
generateProposal({ renderComponent(
id: 'freeform id', generateProposal({
rationale: { id: 'freeform id',
title: 'freeform', rationale: {
}, 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', () => {
renderComponent( render(
generateProposal({ renderComponent(
terms: { generateProposal({
change: { terms: {
__typename: 'UpdateAsset', change: {
assetId: 'foo', __typename: 'UpdateAsset',
assetId: 'foo',
},
}, },
}, })
}) )
); );
expect(screen.getByTestId('proposal-type')).toHaveTextContent( expect(screen.getByTestId('proposal-type')).toHaveTextContent(
'Update asset' 'Update asset'
); );
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Update asset'
);
expect(screen.getByText('foo')).toBeInTheDocument(); expect(screen.getByText('foo')).toBeInTheDocument();
}); });
it("Renders unknown proposal if it's a different proposal type", () => { it("Renders unknown proposal if it's a different proposal type", () => {
renderComponent( render(
generateProposal({ renderComponent(
terms: { generateProposal({
change: { terms: {
// @ts-ignore unknown proposal change: {
__typename: 'Foo', // @ts-ignore unknown proposal
__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,27 +1,23 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Lozenge } from '@vegaprotocol/ui-toolkit'; import { Intent, 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';
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 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 = ({ export const ProposalHeader = ({
proposal, proposal,
isListItem = true, useSubHeading = true,
}: { }: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal']; proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
isListItem?: boolean; useSubHeading?: boolean;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const change = proposal?.terms.change; const change = proposal?.terms.change;
let details: ReactNode; let details: ReactNode;
let proposalType = ''; let proposalType: ReactNode;
let title = proposal?.rationale.title.trim(); let title = proposal?.rationale.title.trim();
let description = proposal?.rationale.description.trim(); let description = proposal?.rationale.description.trim();
@@ -34,12 +30,10 @@ export const ProposalHeader = ({
switch (change?.__typename) { switch (change?.__typename) {
case 'NewMarket': { case 'NewMarket': {
proposalType = 'NewMarket'; proposalType = t('NewMarket');
details = ( details = (
<> <>
<span> {t('Code')}: {change.instrument.code}.{' '}
{t('Code')}: {change.instrument.code}.
</span>{' '}
{change.instrument.futureProduct?.settlementAsset.symbol ? ( {change.instrument.futureProduct?.settlementAsset.symbol ? (
<> <>
<span className="font-semibold"> <span className="font-semibold">
@@ -55,61 +49,54 @@ export const ProposalHeader = ({
break; break;
} }
case 'UpdateMarket': { case 'UpdateMarket': {
proposalType = 'UpdateMarket'; proposalType = t('UpdateMarket');
details = ( details = `${t('Market change')}: ${change.marketId}`;
<>
<span>{t('Market change')}:</span>{' '}
<span>{truncateMiddle(change.marketId)}</span>
</>
);
break; break;
} }
case 'NewAsset': { case 'NewAsset': {
proposalType = 'NewAsset'; proposalType = t('NewAsset');
details = ( details = (
<> <>
<span>{t('Symbol')}:</span> <Lozenge>{change.symbol}.</Lozenge>{' '} {t('Symbol')}: {change.symbol}.{' '}
{change.source.__typename === 'ERC20' && ( <Lozenge>
<> {change.source.__typename === 'ERC20' &&
<span>{t('ERC20ContractAddress')}:</span>{' '} `ERC20 ${change.source.contractAddress}`}
<Lozenge>{change.source.contractAddress}</Lozenge> {change.source.__typename === 'BuiltinAsset' &&
</> `${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 = 'NetworkParameter'; proposalType = t('NetworkParameter');
const parametersClasses = 'font-mono leading-none';
details = ( details = (
<> <>
<span>{t('Change')}:</span>{' '} <span className={`${parametersClasses} mr-2`}>
<Lozenge>{change.networkParameter.key}</Lozenge>{' '} {change.networkParameter.key}
<span>{t('to')}</span>{' '} </span>{' '}
<span className="whitespace-nowrap"> {t('to')}{' '}
<Lozenge>{change.networkParameter.value}</Lozenge> <span className={`${parametersClasses} ml-2`}>
{change.networkParameter.value}
</span> </span>
</> </>
); );
break; break;
} }
case 'NewFreeform': { case 'NewFreeform': {
proposalType = 'Freeform'; proposalType = t('Freeform');
details = <span />; details = `${t('FreeformProposal')}: ${proposal?.id}`;
break; break;
} }
case 'UpdateAsset': { case 'UpdateAsset': {
proposalType = 'UpdateAsset'; proposalType = t('UpdateAsset');
details = ( details = (
<> <>
<span>{t('AssetID')}:</span>{' '} `${t('Update asset')}`;
<Lozenge>{truncateMiddle(change.assetId)}</Lozenge> <Lozenge>{change.assetId}</Lozenge>
</> </>
); );
break; break;
@@ -117,9 +104,9 @@ export const ProposalHeader = ({
} }
return ( return (
<> <div className="text-sm mb-2">
<div data-testid="proposal-title"> <div data-testid="proposal-title">
{isListItem ? ( {useSubHeading ? (
<header> <header>
<SubHeading title={titleContent || t('Unknown proposal')} /> <SubHeading title={titleContent || t('Unknown proposal')} />
</header> </header>
@@ -129,39 +116,18 @@ export const ProposalHeader = ({
</div> </div>
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<div data-testid="proposal-type"> {proposalType && (
<ProposalInfoLabel variant="secondary"> <div data-testid="proposal-type">
{t(`${proposalType}`)} <Lozenge variant={Intent.None}>{proposalType}</Lozenge>
</ProposalInfoLabel> </div>
</div> )}
<div data-testid="proposal-status"> {description && (
<CurrentProposalState proposal={proposal} /> <div data-testid="proposal-description">{description}</div>
</div> )}
</div> </div>
{details && ( {details && <div data-testid="proposal-details">{details}</div>}
<div data-testid="proposal-details" className="break-words my-10"> </div>
{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>
)}
</>
); );
}; };
@@ -1 +0,0 @@
export * from './proposal-info-label';
@@ -1,35 +0,0 @@
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,10 +1,8 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit'; import { 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,
@@ -12,26 +10,10 @@ 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>
<button <SubHeading title={t('proposalTerms')} />
onClick={() => setShowDetails(!showDetails)} <SyntaxHighlighter data={terms} />
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, fireEvent } from '@testing-library/react'; import { render, screen } 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,7 +46,6 @@ 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();
@@ -56,7 +55,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 tokens voted')).toBeInTheDocument(); expect(screen.getByText('Total yes tokens')).toBeInTheDocument();
expect( expect(
screen.getByText('Total tokens voted percentage') screen.getByText('Total tokens voted percentage')
).toBeInTheDocument(); ).toBeInTheDocument();
@@ -71,14 +70,13 @@ 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 tokens voted')).toBeNull(); expect(screen.queryByText('Total yes tokens')).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();
@@ -88,7 +86,6 @@ 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();
}); });
@@ -113,7 +110,6 @@ 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,12 +1,9 @@
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';
@@ -29,7 +26,6 @@ export const ProposalVotesTable = ({
const { const {
appState: { totalSupply }, appState: { totalSupply },
} = useAppState(); } = useAppState();
const [showDetails, setShowDetails] = useState(false);
const { const {
willPassByTokenVote, willPassByTokenVote,
willPassByLPVote, willPassByLPVote,
@@ -57,130 +53,113 @@ export const ProposalVotesTable = ({
? t('byTokenVote') ? t('byTokenVote')
: t('byLiquidityVote'); : t('byLiquidityVote');
const showDetailsIconClasses = classnames('mb-4', {
'rotate-180': showDetails,
});
return ( return (
<> <>
<button <SubHeading title={t('voteBreakdown')} />
onClick={() => setShowDetails(!showDetails)} <RoundedWrapper>
data-testid="vote-breakdown-toggle" <KeyValueTable
> data-testid="proposal-votes-table"
<div className="flex items-center gap-3"> numerical={true}
<SubHeading title={t('voteBreakdown')} /> headingLevel={4}
<div className={showDetailsIconClasses}> >
<Icon name="chevron-down" size={8} /> <KeyValueTableRow>
</div> {t('expectedToPass')}
</div> {isUpdateMarket ? (
</button> updateMarketWillPass ? (
<Thumbs up={true} text={updateMarketVotePassMethod} />
{showDetails && ( ) : (
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}> <Thumbs up={false} />
<KeyValueTable )
data-testid="proposal-votes-table" ) : willPassByTokenVote ? (
numerical={true} <Thumbs up={true} />
headingLevel={4} ) : (
> <Thumbs up={false} />
)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('majorityMet')}
{majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow> <KeyValueTableRow>
{t('expectedToPass')} {t('majorityLPMet')}
{isUpdateMarket ? ( {majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
updateMarketWillPass ? ( </KeyValueTableRow>
<Thumbs up={true} text={updateMarketVotePassMethod} /> )}
) : ( <KeyValueTableRow>
<Thumbs up={false} /> {t('participationMet')}
) {participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
) : willPassByTokenVote ? ( </KeyValueTableRow>
{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('majorityMet')} {t('tokenLPForProposal')}
{majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />} {formatNumber(yesEquityLikeShareWeight, 2)}
</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('majorityLPMet')} {t('numberOfVotingParties')}
{majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />} {formatNumber(totalVotes, 0)}
</KeyValueTableRow> </KeyValueTableRow>
)}
<KeyValueTableRow>
{t('participationMet')}
{participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow> <KeyValueTableRow>
{t('participationLPMet')} {t('totalTokensVotes')}
{participationLPMet ? ( {formatNumber(totalTokensVoted, 2)}
<Thumbs up={true} />
) : (
<Thumbs up={false} />
)}
</KeyValueTableRow> </KeyValueTableRow>
)}
<KeyValueTableRow>
{t('tokenForProposal')}
{formatNumber(yesTokens, 2)}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow> <KeyValueTableRow>
{t('tokenLPForProposal')} {t('totalTokenVotedPercentage')}
{formatNumber(yesEquityLikeShareWeight, 2)} {formatNumberPercentage(totalTokensPercentage, 2)}
</KeyValueTableRow> </KeyValueTableRow>
)} <KeyValueTableRow>
<KeyValueTableRow> {t('numberOfForVotes')}
{t('totalSupply')} {formatNumber(yesVotes, 0)}
{formatNumber(totalSupply, 2)} </KeyValueTableRow>
</KeyValueTableRow> <KeyValueTableRow>
<KeyValueTableRow> {t('numberOfAgainstVotes')}
{t('tokensAgainstProposal')} {formatNumber(noVotes, 0)}
{formatNumber(noTokens, 2)} </KeyValueTableRow>
</KeyValueTableRow> <KeyValueTableRow>
<KeyValueTableRow> {t('yesPercentage')}
{t('participationRequired')} {formatNumberPercentage(yesPercentage, 2)}
{formatNumberPercentage(requiredParticipation)} </KeyValueTableRow>
</KeyValueTableRow> <KeyValueTableRow noBorder={true}>
<KeyValueTableRow> {t('noPercentage')}
{t('majorityRequired')} {formatNumberPercentage(noPercentage, 2)}
{formatNumberPercentage(requiredMajorityPercentage)} </KeyValueTableRow>
</KeyValueTableRow> </>
{!isUpdateMarket && ( )}
<> </KeyValueTable>
<KeyValueTableRow> </RoundedWrapper>
{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>
)}
</> </>
); );
}; };
@@ -3,8 +3,8 @@ import { generateProposal } from '../../test-helpers/generate-proposals';
import { Proposal } from './proposal'; import { Proposal } from './proposal';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
jest.mock('@vegaprotocol/network-parameters', () => ({ jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'), ...jest.requireActual('@vegaprotocol/react-helpers'),
useNetworkParams: jest.fn(() => ({ useNetworkParams: jest.fn(() => ({
params: { params: {
governance_proposal_asset_minVoterBalance: '1', governance_proposal_asset_minVoterBalance: '1',
@@ -1,8 +1,5 @@
import { import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
NetworkParams, import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
useNetworkParams,
} from '@vegaprotocol/network-parameters';
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';
@@ -77,8 +74,8 @@ export const Proposal = ({ proposal }: ProposalProps) => {
return ( return (
<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} useSubHeading={false} />
<div className="my-10"> <div className="mb-10">
<ProposalChangeTable proposal={proposal} /> <ProposalChangeTable proposal={proposal} />
</div> </div>
{proposal.terms.change.__typename === 'NewAsset' && {proposal.terms.change.__typename === 'NewAsset' &&
@@ -91,18 +88,14 @@ export const Proposal = ({ proposal }: ProposalProps) => {
/> />
) : null} ) : null}
<div className="mb-12"> <div className="mb-12">
<RoundedWrapper paddingBottom={true}> <VoteDetails
<VoteDetails proposal={proposal}
proposal={proposal} proposalType={proposalType}
proposalType={proposalType} minVoterBalance={minVoterBalance}
minVoterBalance={minVoterBalance} spamProtectionMinTokens={params?.spam_protection_voting_min_tokens}
spamProtectionMinTokens={ />
params?.spam_protection_voting_min_tokens
}
/>
</RoundedWrapper>
</div> </div>
<div className="mb-4"> <div className="mb-10">
<ProposalVotesTable proposal={proposal} proposalType={proposalType} /> <ProposalVotesTable proposal={proposal} proposalType={proposalType} />
</div> </div>
<ProposalTermsJson terms={proposal.terms} /> <ProposalTermsJson terms={proposal.terms} />
@@ -97,6 +97,7 @@ 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)
); );
@@ -112,6 +113,7 @@ 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)}`
); );
@@ -126,6 +128,9 @@ 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)}`
); );
@@ -216,6 +221,7 @@ 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'
); );
@@ -230,6 +236,7 @@ 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'
); );
@@ -244,6 +251,7 @@ 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'
); );
@@ -260,7 +268,10 @@ describe('Proposals list item details', () => {
networkParamsQueryMock, networkParamsQueryMock,
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_YES), createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_YES),
]); ]);
expect(await screen.findByText('You voted For')).toBeInTheDocument(); expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
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 () => {
@@ -274,7 +285,9 @@ describe('Proposals list item details', () => {
networkParamsQueryMock, networkParamsQueryMock,
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_NO), createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_NO),
]); ]);
expect(await screen.findByText('You voted Against')).toBeInTheDocument(); expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
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', () => {
@@ -290,6 +303,7 @@ 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'
); );
@@ -308,6 +322,7 @@ 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'
); );
@@ -327,6 +342,7 @@ 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');
}); });
@@ -343,6 +359,7 @@ 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'
); );
@@ -361,6 +378,7 @@ 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'
); );
@@ -377,6 +395,7 @@ 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,8 +1,11 @@
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Button } from '@vegaprotocol/ui-toolkit'; import { Button, Icon } 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 { StatusPass } from '../current-proposal-status/current-proposal-status'; import {
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';
@@ -19,7 +22,7 @@ const MajorityNotReached = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<> <>
{t('Majority')} {t('not reached')} {t('Majority')} <StatusFail>{t('not reached')}</StatusFail>
</> </>
); );
}; };
@@ -27,7 +30,7 @@ const ParticipationNotReached = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<> <>
{t('Participation')} {t('not reached')} {t('Participation')} <StatusFail>{t('not reached')}</StatusFail>
</> </>
); );
}; };
@@ -54,11 +57,17 @@ 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(
@@ -70,6 +79,11 @@ 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')}{' '}
@@ -83,6 +97,11 @@ 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')}{' '}
@@ -96,14 +115,19 @@ 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')} {t('voteState_Yes')} {t('youVoted')} <StatusPass>{t('voteState_Yes')}</StatusPass>
</> </>
)) || )) ||
(voteState === 'No' && ( (voteState === 'No' && (
<> <>
{t('youVoted')} {t('voteState_No')} {t('youVoted')} <StatusFail>{t('voteState_No')}</StatusFail>
</> </>
)) || ( )) || (
<> <>
@@ -124,29 +148,40 @@ export const ProposalsListItemDetails = ({
</> </>
) : ( ) : (
<> <>
{t('Set to')} {t('fail')} {t('Set to')} <StatusFail>{t('fail')}</StatusFail>
</> </>
))) || ))) ||
(!participationMet && <ParticipationNotReached />) || (!participationMet && <ParticipationNotReached />) ||
(!majorityMet && <MajorityNotReached />) || (!majorityMet && <MajorityNotReached />) ||
(willPassByTokenVote ? ( (willPassByTokenVote ? (
<> <>
{t('Set to')} {t('pass')} {t('Set to')} <StatusPass>{t('pass')}</StatusPass>
</> </>
) : ( ) : (
<> <>
{t('Set to')} {t('fail')} {t('Set to')} <StatusFail>{t('fail')}</StatusFail>
</> </>
)); ));
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])}</>
); );
@@ -155,10 +190,16 @@ export const ProposalsListItemDetails = ({
} }
return ( return (
<div className="grid grid-cols-[1fr_auto] mt-4 items-start gap-2 text-sm"> <div className="grid grid-cols-[1fr_auto] mt-2 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-vega-light-300" className="col-start-1 row-start-2 text-neutral-500"
data-testid="vote-details" data-testid="vote-details"
> >
{voteDetails} {voteDetails}
@@ -175,7 +216,9 @@ 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">{t('View')}</Button> <Button data-testid="view-proposal-btn" size="sm">
{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-vega-dark-100"> <section className="-mx-4 p-4 mb-8 bg-neutral-800">
<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 { ProposalInfoLabel } from '../proposal-info-label';
import Routes from '../../../routes';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import Routes from '../../../routes';
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 = (
<span data-testid="protocol-upgrade-proposal-status-icon-rejected"> <div data-testid="protocol-upgrade-proposal-status-icon-rejected">
<Icon name={'cross'} /> <Icon name={'cross'} />
</span> </div>
); );
break; break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING: case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING:
proposalStatusIcon = ( proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-pending"> <div data-testid="protocol-upgrade-proposal-status-icon-pending">
<Icon name={'time'} /> <Icon name={'time'} />
</span> </div>
); );
break; break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED: case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED:
proposalStatusIcon = ( proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-approved"> <div data-testid="protocol-upgrade-proposal-status-icon-approved">
<Icon name={'tick'} /> <Icon name={'tick'} />
</span> </div>
); );
break; break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED: case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED:
proposalStatusIcon = ( proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-unspecified"> <div data-testid="protocol-upgrade-proposal-status-icon-unspecified">
<Icon name={'disable'} /> <Icon name={'disable'} />
</span> </div>
); );
break; break;
} }
@@ -71,28 +71,18 @@ export const ProtocolUpgradeProposalsListItem = ({
</div> </div>
<div className="text-sm"> <div className="text-sm">
<div className="flex gap-2"> <div
<div data-testid="protocol-upgrade-proposal-type"
data-testid="protocol-upgrade-proposal-type" className="flex items-center gap-2 mb-4"
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>{t('vegaReleaseTag')}:</span>{' '} <span className="pr-2">{t('vegaReleaseTag')}</span>
<Lozenge>{proposal.vegaReleaseTag}</Lozenge> <Lozenge>{proposal.vegaReleaseTag}</Lozenge>
</div> </div>
@@ -100,18 +90,30 @@ export const ProtocolUpgradeProposalsListItem = ({
data-testid="protocol-upgrade-proposal-block-height" data-testid="protocol-upgrade-proposal-block-height"
className="mb-2" className="mb-2"
> >
<span>{t('upgradeBlockHeight')}:</span>{' '} <span className="pr-2">{t('upgradeBlockHeight')}</span>
<Lozenge>{proposal.upgradeBlockHeight}</Lozenge> <Lozenge>{proposal.upgradeBlockHeight}</Lozenge>
</div> </div>
<div className="grid grid-cols-1 mt-3"> <div className="grid grid-cols-[1fr_auto] mt-3 items-start gap-2">
<div className="justify-self-end"> <div className="col-start-1 row-start-1 text-white">
<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">{t('View')}</Button> <Button data-testid="view-proposal-btn" size="sm">
{t('View')}
</Button>
</Link> </Link>
</div> </div>
</div> </div>
@@ -107,6 +107,10 @@ 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)
@@ -159,30 +163,24 @@ export const VoteButtons = ({
return ( return (
<> <>
{changeVote || (voteState === VoteState.NotCast && proposalVotable) ? ( {changeVote || (voteState === VoteState.NotCast && proposalVotable) ? (
<> <div className="flex gap-4" data-testid="vote-buttons">
{currentStakeAvailable.isLessThanOrEqualTo(0) && ( <div className="flex-1">
<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,6 +1,5 @@
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';
@@ -200,11 +199,10 @@ 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('castYourVote')} /> <SubHeading title={t('yourVote')} />
{pubKey ? ( {proposal && (
proposal && (
<VoteButtonsContainer <VoteButtonsContainer
voteState={voteState} voteState={voteState}
voteDatetime={voteDatetime} voteDatetime={voteDatetime}
@@ -216,19 +214,11 @@ export const VoteDetails = ({
submit={submit} submit={submit}
dialog={Dialog} dialog={Dialog}
/> />
) )}
) : ( </section>
<RoundedWrapper paddingBottom={true}> ) : (
<div className="mb-4"> <ConnectToVega />
<div className="flex items-center gap-2 mb-2"> )}
<Icon name={'info-sign'} />
<div>{t('connectAVegaWalletToVote')}</div>
</div>
</div>
<ConnectToVega />
</RoundedWrapper>
)}
</section>
</section> </section>
</> </>
); );
@@ -3,8 +3,8 @@ import { BigNumber } from '../../../lib/bignumber';
import { useProposalNetworkParams } from './use-proposal-network-params'; import { useProposalNetworkParams } from './use-proposal-network-params';
import { generateProposal } from '../test-helpers/generate-proposals'; import { generateProposal } from '../test-helpers/generate-proposals';
jest.mock('@vegaprotocol/network-parameters', () => ({ jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'), ...jest.requireActual('@vegaprotocol/react-helpers'),
useNetworkParams: jest.fn(() => ({ useNetworkParams: jest.fn(() => ({
params: { params: {
governance_proposal_updateMarket_requiredMajority: '0.1', governance_proposal_updateMarket_requiredMajority: '0.1',
@@ -1,7 +1,4 @@
import { import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { BigNumber } from '../../../lib/bignumber'; import { BigNumber } from '../../../lib/bignumber';
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';
@@ -28,8 +28,8 @@ jest.mock('../../../contexts/app-state/app-state-context', () => ({
}), }),
})); }));
jest.mock('@vegaprotocol/network-parameters', () => ({ jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'), ...jest.requireActual('@vegaprotocol/react-helpers'),
useNetworkParams: jest.fn(() => ({ useNetworkParams: jest.fn(() => ({
params: { params: {
governance_proposal_updateMarket_requiredMajority: '0.5', governance_proposal_updateMarket_requiredMajority: '0.5',
@@ -5,9 +5,9 @@ import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet'; import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MemoryRouter as Router } from 'react-router-dom'; import { MemoryRouter as Router } from 'react-router-dom';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/react-helpers';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({ useEnvironment: () => ({
@@ -19,10 +19,7 @@ import { ProposalMinRequirements } from '../../components/shared';
import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading'; import { Heading } from '../../../../components/heading';
import { createDocsLinks } from '@vegaprotocol/utils'; import { createDocsLinks } from '@vegaprotocol/utils';
import { import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { ProposalUserAction } from '../../components/shared'; import { ProposalUserAction } from '../../components/shared';
import { downloadJson } from '../../../../lib/download-json'; import { downloadJson } from '../../../../lib/download-json';
@@ -5,8 +5,8 @@ import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet'; import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MemoryRouter as Router } from 'react-router-dom'; import { MemoryRouter as Router } from 'react-router-dom';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/react-helpers';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
@@ -5,7 +5,7 @@ import {
createDocsLinks, createDocsLinks,
suitableForSyntaxHighlighter, suitableForSyntaxHighlighter,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { useNetworkParams } from '@vegaprotocol/network-parameters'; import { useNetworkParams } from '@vegaprotocol/react-helpers';
import { import {
getClosingTimestamp, getClosingTimestamp,
getEnactmentTimestamp, getEnactmentTimestamp,
@@ -5,9 +5,9 @@ import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks'; import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeNewAsset } from './propose-new-asset'; import { ProposeNewAsset } from './propose-new-asset';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/react-helpers';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({ useEnvironment: () => ({
@@ -9,10 +9,7 @@ import {
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { createDocsLinks, validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { import {
ProposalFormDescription, ProposalFormDescription,
ProposalFormSubheader, ProposalFormSubheader,
@@ -6,8 +6,8 @@ import { AppStateProvider } from '../../../../contexts/app-state/app-state-provi
import { VegaWalletContext } from '@vegaprotocol/wallet'; import { VegaWalletContext } from '@vegaprotocol/wallet';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/react-helpers';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({ useEnvironment: () => ({
@@ -8,10 +8,7 @@ import {
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { createDocsLinks, validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { import {
ProposalFormDescription, ProposalFormDescription,
ProposalFormSubheader, ProposalFormSubheader,
@@ -10,8 +10,8 @@ import { ProposeRaw } from './propose-raw';
import { ProposalEventDocument } from '@vegaprotocol/proposals'; import { ProposalEventDocument } from '@vegaprotocol/proposals';
import type { ProposalEventSubscription } from '@vegaprotocol/proposals'; import type { ProposalEventSubscription } from '@vegaprotocol/proposals';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/react-helpers';
const paramsDelay = 20; const paramsDelay = 20;
@@ -10,10 +10,7 @@ import {
TextArea, TextArea,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { createDocsLinks, validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { useProposalSubmit } from '@vegaprotocol/proposals'; import { useProposalSubmit } from '@vegaprotocol/proposals';
import { import {
ProposalFormSubmit, ProposalFormSubmit,
@@ -5,9 +5,9 @@ import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks'; import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeUpdateAsset } from './propose-update-asset'; import { ProposeUpdateAsset } from './propose-update-asset';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/react-helpers';
jest.mock('@vegaprotocol/environment', () => ({ jest.mock('@vegaprotocol/environment', () => ({
useEnvironment: () => ({ useEnvironment: () => ({
@@ -8,10 +8,7 @@ import {
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { createDocsLinks, validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { import {
ProposalFormDescription, ProposalFormDescription,
ProposalFormSubheader, ProposalFormSubheader,
@@ -6,8 +6,8 @@ import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider'; import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks'; import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeUpdateMarket } from './propose-update-market'; import { ProposeUpdateMarket } from './propose-update-market';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/react-helpers';
import type { ProposalMarketsQueryQuery } from './__generated__/UpdateMarket'; import type { ProposalMarketsQueryQuery } from './__generated__/UpdateMarket';
import { ProposalMarketsQueryDocument } from './__generated__/UpdateMarket'; import { ProposalMarketsQueryDocument } from './__generated__/UpdateMarket';
import { ProposalState } from '@vegaprotocol/types'; import { ProposalState } from '@vegaprotocol/types';
@@ -9,10 +9,7 @@ import {
} from '@vegaprotocol/proposals'; } from '@vegaprotocol/proposals';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { createDocsLinks, validateJson } from '@vegaprotocol/utils'; import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
import { import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { import {
ProposalFormDescription, ProposalFormDescription,
ProposalFormDownloadJson, ProposalFormDownloadJson,
@@ -1,6 +1,6 @@
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters'; import { NetworkParamsDocument } from '@vegaprotocol/react-helpers';
import type { MockedResponse } from '@apollo/client/testing'; import type { MockedResponse } from '@apollo/client/testing';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters'; import type { NetworkParamsQuery } from '@vegaprotocol/react-helpers';
import type { PubKey } from '@vegaprotocol/wallet'; import type { PubKey } from '@vegaprotocol/wallet';
export const mockPubkey: PubKey = { export const mockPubkey: PubKey = {
@@ -34,9 +34,8 @@ export const useUserTrancheBalances = (address: string | undefined) => {
vesting.get_vested_for_tranche(address, tId), vesting.get_vested_for_tranche(address, tId),
]); ]);
// Convert t and v EthersBigNumbers to regular BigNumbers const total = toBigNum(t, decimals);
const total = toBigNum(t.toString(), decimals); const vested = toBigNum(v, decimals);
const vested = toBigNum(v.toString(), decimals);
return { return {
id: tId, id: tId,
@@ -12,10 +12,7 @@ import {
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { createDocsLinks } from '@vegaprotocol/utils'; import { createDocsLinks } from '@vegaprotocol/utils';
import { import { useNetworkParams, NetworkParams } from '@vegaprotocol/react-helpers';
useNetworkParams,
NetworkParams,
} from '@vegaprotocol/network-parameters';
import { useEpochQuery } from './__generated__/Rewards'; import { useEpochQuery } from './__generated__/Rewards';
import { EpochCountdown } from '../../../components/epoch-countdown'; import { EpochCountdown } from '../../../components/epoch-countdown';
import { Heading, SubHeading } from '../../../components/heading'; import { Heading, SubHeading } from '../../../components/heading';

Some files were not shown because too many files have changed in this diff Show More