Compare commits

..
151 changed files with 1559 additions and 2316 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
@@ -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">
@@ -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>
@@ -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>
</>
); );
}; };
@@ -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}
@@ -21,7 +21,6 @@ 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;
@@ -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>
);
}; };
@@ -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>
)}
</> </>
); );
}; };
@@ -2,7 +2,7 @@ import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
} from '@vegaprotocol/network-parameters'; } from '@vegaprotocol/network-parameters';
import { AsyncRenderer, RoundedWrapper } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } 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 +77,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 +91,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>
</> </>
); );
@@ -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,
@@ -54,7 +54,7 @@ export const WalletAssociate = ({
address, address,
ethereumConfig.staking_bridge_contract.address ethereumConfig.staking_bridge_contract.address
); );
const allowance = toBigNum(a.toString(), decimals); const allowance = toBigNum(a, decimals);
setAllowance(allowance); setAllowance(allowance);
} }
}; };
-3
View File
@@ -6,7 +6,6 @@ import '@testing-library/jest-dom';
import dev from './i18n/translations/dev.json'; import dev from './i18n/translations/dev.json';
import i18n from 'i18next'; import i18n from 'i18next';
import { initReactI18next } from 'react-i18next'; import { initReactI18next } from 'react-i18next';
import ResizeObserver from 'resize-observer-polyfill';
// Set up i18n instance so that components have the correct default // Set up i18n instance so that components have the correct default
// en translations // en translations
@@ -23,5 +22,3 @@ i18n.use(initReactI18next).init({
ns: ['translations'], ns: ['translations'],
defaultNS: 'translations', defaultNS: 'translations',
}); });
global.ResizeObserver = ResizeObserver;
-41
View File
@@ -40,11 +40,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;
}
/* Dark variables */ /* Dark variables */
.ag-theme-balham-dark { .ag-theme-balham-dark {
--ag-background-color: theme(colors.black); --ag-background-color: theme(colors.black);
@@ -63,39 +58,3 @@
.validators-table .ag-theme-balham-dark *:hover { .validators-table .ag-theme-balham-dark *:hover {
cursor: pointer; cursor: pointer;
} }
/* Styles required to (effectively) un-override the
* reset styles so that the Proposal description fields
* render as you'd expect them to.
*
* Notes:
* - image embeds are disabled, so no styles are required
* - strong may not be required
* - skipHTML is enabled, so no nested HTML will be rendered. Only
* . valid markdown
*/
.dark .react-markdown-container,
.dark .react-markdown-container li,
.dark .react-markdown-container p {
color: #fff;
}
.react-markdown-container strong {
font-weight: bold;
}
.react-markdown-container ol {
margin-left: 1em;
}
.react-markdown-container li {
margin-left: 1em;
padding-left: 1em;
}
.react-markdown-container ol li {
list-style: decimal;
}
.react-markdown-container ul li {
list-style: circle;
}
@@ -1,6 +1,6 @@
# App configuration variables # App configuration variables
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_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\"} NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\"}
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables # App configuration variables
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='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}' NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
+43 -190
View File
@@ -48,8 +48,8 @@
"tranche_start": "2023-04-20T00:00:00.000Z", "tranche_start": "2023-04-20T00:00:00.000Z",
"tranche_end": "2023-05-20T00:00:00.000Z", "tranche_end": "2023-05-20T00:00:00.000Z",
"total_added": "19242.125", "total_added": "19242.125",
"total_removed": "1523.8177488329475", "total_removed": "1214.9959502191275",
"locked_amount": "6891.9471601080244395475", "locked_amount": "9295.5944273726850283975",
"deposits": [ "deposits": [
{ {
"amount": "188", "amount": "188",
@@ -223,11 +223,6 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "308.82179861382",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
"tx": "0x2b3571c143ecebddf91fb62f402d516d51110edfe37b13200a6e5cf682dc5bb0"
},
{ {
"amount": "202.093666077975", "amount": "202.093666077975",
"user": "0x1447Efc62d5077d7AbB20492dF831Dd6c9Eb1756", "user": "0x1447Efc62d5077d7AbB20492dF831Dd6c9Eb1756",
@@ -356,12 +351,6 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "308.82179861382",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
"tranche_id": 56,
"tx": "0x2b3571c143ecebddf91fb62f402d516d51110edfe37b13200a6e5cf682dc5bb0"
},
{ {
"amount": "195.89040769089", "amount": "195.89040769089",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605", "user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
@@ -370,8 +359,8 @@
} }
], ],
"total_tokens": "914.25", "total_tokens": "914.25",
"withdrawn_tokens": "504.71220630471", "withdrawn_tokens": "195.89040769089",
"remaining_tokens": "409.53779369529" "remaining_tokens": "718.35959230911"
}, },
{ {
"address": "0x9573BDF7FfC5519912d293e4D1f750eab2E471E7", "address": "0x9573BDF7FfC5519912d293e4D1f750eab2E471E7",
@@ -876,15 +865,10 @@
"tranche_id": 54, "tranche_id": 54,
"tranche_start": "2023-04-06T00:00:00.000Z", "tranche_start": "2023-04-06T00:00:00.000Z",
"tranche_end": "2023-05-06T00:00:00.000Z", "tranche_end": "2023-05-06T00:00:00.000Z",
"total_added": "14610", "total_added": "14520",
"total_removed": "6141.45090157707", "total_removed": "5079.76864115505",
"locked_amount": "0", "locked_amount": "238.4036111111108718",
"deposits": [ "deposits": [
{
"amount": "90",
"user": "0x05659B08a8079E003eE37F26c29e52532728c034",
"tx": "0x83cb92910e0725b82b3459e025260c448f931224f7cb00a376351dbd75ae2733"
},
{ {
"amount": "111", "amount": "111",
"user": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241", "user": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241",
@@ -1037,46 +1021,6 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "191.85071991228",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
"tx": "0x1438ad7d4c51ec6cb9b8f9d67b05583915099427035179baf996fd208fd76603"
},
{
"amount": "309",
"user": "0x21770cCAb229Bd1509609fFFa35bEB16b73A657c",
"tx": "0x264f4b3c5dc43cd5afaa5954b50ba7885ce3091b11ae51ba19b8a5851a5a4fc7"
},
{
"amount": "116.83154050974",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
"tx": "0x130c439ea764588c13cd825ae89767bea04064bd3d76b762b7148e5373857b9b"
},
{
"amount": "0",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
"tx": "0x9be84231ff156bc8b8de9a99250f03b8ebf9d59c252e3b778f10051489e5758a"
},
{
"amount": "111",
"user": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241",
"tx": "0xdfae5add3e12eed919c0481d9289aa33688884db68ca90332d1df31107e4fbc6"
},
{
"amount": "111",
"user": "0xAc1c2783d64c29CDB79608f5B66a37Fd1ea35dD6",
"tx": "0x7195dc5808870b0f00824c6593eda1e994976189cf3373cf84c5dae7a60d8b62"
},
{
"amount": "90",
"user": "0x05659B08a8079E003eE37F26c29e52532728c034",
"tx": "0x016970cfdaaab7a663a5c8b897b44c5d302e5cd110638de231073c3b5e23ddf8"
},
{
"amount": "132",
"user": "0x9E53F72210479BF46aE7ABF77c48B0d611b3c3dB",
"tx": "0x1dbcf713b48965a82aa2e17cb3e7db9a491668d859d408a39c8a74b0ea860b6b"
},
{ {
"amount": "106.53500000286", "amount": "106.53500000286",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9", "user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
@@ -1124,28 +1068,6 @@
} }
], ],
"users": [ "users": [
{
"address": "0x05659B08a8079E003eE37F26c29e52532728c034",
"deposits": [
{
"amount": "90",
"user": "0x05659B08a8079E003eE37F26c29e52532728c034",
"tranche_id": 54,
"tx": "0x83cb92910e0725b82b3459e025260c448f931224f7cb00a376351dbd75ae2733"
}
],
"withdrawals": [
{
"amount": "90",
"user": "0x05659B08a8079E003eE37F26c29e52532728c034",
"tranche_id": 54,
"tx": "0x016970cfdaaab7a663a5c8b897b44c5d302e5cd110638de231073c3b5e23ddf8"
}
],
"total_tokens": "90",
"withdrawn_tokens": "90",
"remaining_tokens": "0"
},
{ {
"address": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241", "address": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241",
"deposits": [ "deposits": [
@@ -1156,17 +1078,10 @@
"tx": "0x1229e077f48b796678436bfa8143cea3ae71df58b589ac922be205c4482be235" "tx": "0x1229e077f48b796678436bfa8143cea3ae71df58b589ac922be205c4482be235"
} }
], ],
"withdrawals": [ "withdrawals": [],
{
"amount": "111",
"user": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241",
"tranche_id": 54,
"tx": "0xdfae5add3e12eed919c0481d9289aa33688884db68ca90332d1df31107e4fbc6"
}
],
"total_tokens": "111", "total_tokens": "111",
"withdrawn_tokens": "111", "withdrawn_tokens": "0",
"remaining_tokens": "0" "remaining_tokens": "111"
}, },
{ {
"address": "0x83D7eD53E7CB97b542F1F71f40561d51F8019C8B", "address": "0x83D7eD53E7CB97b542F1F71f40561d51F8019C8B",
@@ -1411,12 +1326,6 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "191.85071991228",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
"tranche_id": 54,
"tx": "0x1438ad7d4c51ec6cb9b8f9d67b05583915099427035179baf996fd208fd76603"
},
{ {
"amount": "106.53500000286", "amount": "106.53500000286",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9", "user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
@@ -1455,8 +1364,8 @@
} }
], ],
"total_tokens": "858", "total_tokens": "858",
"withdrawn_tokens": "858", "withdrawn_tokens": "666.14928008772",
"remaining_tokens": "0" "remaining_tokens": "191.85071991228"
}, },
{ {
"address": "0x0bBf7580e036eA5D69ABe679CC90117EeC2e3dc1", "address": "0x0bBf7580e036eA5D69ABe679CC90117EeC2e3dc1",
@@ -1484,18 +1393,6 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "116.83154050974",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
"tranche_id": 54,
"tx": "0x130c439ea764588c13cd825ae89767bea04064bd3d76b762b7148e5373857b9b"
},
{
"amount": "0",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
"tranche_id": 54,
"tx": "0x9be84231ff156bc8b8de9a99250f03b8ebf9d59c252e3b778f10051489e5758a"
},
{ {
"amount": "60.16845949026", "amount": "60.16845949026",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7", "user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
@@ -1504,8 +1401,8 @@
} }
], ],
"total_tokens": "177", "total_tokens": "177",
"withdrawn_tokens": "177", "withdrawn_tokens": "60.16845949026",
"remaining_tokens": "0" "remaining_tokens": "116.83154050974"
}, },
{ {
"address": "0xAc1c2783d64c29CDB79608f5B66a37Fd1ea35dD6", "address": "0xAc1c2783d64c29CDB79608f5B66a37Fd1ea35dD6",
@@ -1517,17 +1414,10 @@
"tx": "0x99aeaedef27b5fb693485f4e21d434e343b30fa100945a4ff65386de8801c87e" "tx": "0x99aeaedef27b5fb693485f4e21d434e343b30fa100945a4ff65386de8801c87e"
} }
], ],
"withdrawals": [ "withdrawals": [],
{
"amount": "111",
"user": "0xAc1c2783d64c29CDB79608f5B66a37Fd1ea35dD6",
"tranche_id": 54,
"tx": "0x7195dc5808870b0f00824c6593eda1e994976189cf3373cf84c5dae7a60d8b62"
}
],
"total_tokens": "111", "total_tokens": "111",
"withdrawn_tokens": "111", "withdrawn_tokens": "0",
"remaining_tokens": "0" "remaining_tokens": "111"
}, },
{ {
"address": "0x8D416A61bCccf4E6aF5598302BC4e41f97401652", "address": "0x8D416A61bCccf4E6aF5598302BC4e41f97401652",
@@ -1554,17 +1444,10 @@
"tx": "0xcb0f255003872ac506798efa97744868e11ceab2f2f03d96da605d8674f783f6" "tx": "0xcb0f255003872ac506798efa97744868e11ceab2f2f03d96da605d8674f783f6"
} }
], ],
"withdrawals": [ "withdrawals": [],
{
"amount": "132",
"user": "0x9E53F72210479BF46aE7ABF77c48B0d611b3c3dB",
"tranche_id": 54,
"tx": "0x1dbcf713b48965a82aa2e17cb3e7db9a491668d859d408a39c8a74b0ea860b6b"
}
],
"total_tokens": "132", "total_tokens": "132",
"withdrawn_tokens": "132", "withdrawn_tokens": "0",
"remaining_tokens": "0" "remaining_tokens": "132"
}, },
{ {
"address": "0xC0f02AA8bA8b509D223C9De6108AFA1C03f03341", "address": "0xC0f02AA8bA8b509D223C9De6108AFA1C03f03341",
@@ -1591,17 +1474,10 @@
"tx": "0xf6467d54c2de5c5d21fd3ac174ac6bf51b6fc675cf81dedcb143679f28caa46a" "tx": "0xf6467d54c2de5c5d21fd3ac174ac6bf51b6fc675cf81dedcb143679f28caa46a"
} }
], ],
"withdrawals": [ "withdrawals": [],
{
"amount": "309",
"user": "0x21770cCAb229Bd1509609fFFa35bEB16b73A657c",
"tranche_id": 54,
"tx": "0x264f4b3c5dc43cd5afaa5954b50ba7885ce3091b11ae51ba19b8a5851a5a4fc7"
}
],
"total_tokens": "309", "total_tokens": "309",
"withdrawn_tokens": "309", "withdrawn_tokens": "0",
"remaining_tokens": "0" "remaining_tokens": "309"
}, },
{ {
"address": "0x0629D3C608e1E8e9B88a1B00e1422e4B33276a98", "address": "0x0629D3C608e1E8e9B88a1B00e1422e4B33276a98",
@@ -4861,7 +4737,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z", "tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "86666.297", "total_added": "86666.297",
"total_removed": "0", "total_removed": "0",
"locked_amount": "49802.2753121154213651374", "locked_amount": "50692.0833719598561288655",
"deposits": [ "deposits": [
{ {
"amount": "86666.297", "amount": "86666.297",
@@ -4927,7 +4803,7 @@
"tranche_end": "2023-06-01T00:00:00.000Z", "tranche_end": "2023-06-01T00:00:00.000Z",
"total_added": "2500", "total_added": "2500",
"total_removed": "0", "total_removed": "0",
"locked_amount": "312.4325905575906", "locked_amount": "363.908920940171075",
"deposits": [ "deposits": [
{ {
"amount": "2500", "amount": "2500",
@@ -4960,7 +4836,7 @@
"tranche_end": "2023-11-01T00:00:00.000Z", "tranche_end": "2023-11-01T00:00:00.000Z",
"total_added": "15000.000000000000015", "total_added": "15000.000000000000015",
"total_removed": "0", "total_removed": "0",
"locked_amount": "14327.0455917874395143270455917874395", "locked_amount": "14632.5464221014495146325464221014495",
"deposits": [ "deposits": [
{ {
"amount": "1.5e-14", "amount": "1.5e-14",
@@ -5048,7 +4924,7 @@
"tranche_end": "2023-09-01T00:00:00.000Z", "tranche_end": "2023-09-01T00:00:00.000Z",
"total_added": "17500", "total_added": "17500",
"total_removed": "0", "total_removed": "0",
"locked_amount": "10913.25608896940375", "locked_amount": "11269.67372433574875",
"deposits": [ "deposits": [
{ {
"amount": "12500", "amount": "12500",
@@ -5315,7 +5191,7 @@
"tranche_end": "2023-08-01T00:00:00.000Z", "tranche_end": "2023-08-01T00:00:00.000Z",
"total_added": "37500", "total_added": "37500",
"total_removed": "18077.0118744", "total_removed": "18077.0118744",
"locked_amount": "17350.5026089625535", "locked_amount": "18126.91355893186125",
"deposits": [ "deposits": [
{ {
"amount": "7500", "amount": "7500",
@@ -5734,7 +5610,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z", "tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45", "total_added": "129999.45",
"total_removed": "0", "total_removed": "0",
"locked_amount": "49756.837002255673748325", "locked_amount": "50645.8332242054391795015",
"deposits": [ "deposits": [
{ {
"amount": "129999.45", "amount": "129999.45",
@@ -5767,7 +5643,7 @@
"tranche_end": "2024-04-01T00:00:00.000Z", "tranche_end": "2024-04-01T00:00:00.000Z",
"total_added": "54144.7663", "total_added": "54144.7663",
"total_removed": "0", "total_removed": "0",
"locked_amount": "48485.46842731635829207292", "locked_amount": "49039.85711135509392504979",
"deposits": [ "deposits": [
{ {
"amount": "54144.7663", "amount": "54144.7663",
@@ -5800,7 +5676,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z", "tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "62600", "total_added": "62600",
"total_removed": "0", "total_removed": "0",
"locked_amount": "20022.5830035514954", "locked_amount": "20665.30095129376038",
"deposits": [ "deposits": [
{ {
"amount": "10000", "amount": "10000",
@@ -5993,7 +5869,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z", "tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000", "total_added": "5000",
"total_removed": "0", "total_removed": "0",
"locked_amount": "1791.028665651953", "locked_amount": "1842.3639649923895",
"deposits": [ "deposits": [
{ {
"amount": "5000", "amount": "5000",
@@ -7062,7 +6938,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z", "tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38", "total_added": "1939928.38",
"total_removed": "1709370.7872515768348", "total_removed": "1709370.7872515768348",
"locked_amount": "126202.1342632826843379468", "locked_amount": "146119.4950805440611341152",
"deposits": [ "deposits": [
{ {
"amount": "1852091.69", "amount": "1852091.69",
@@ -40934,7 +40810,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671", "total_added": "3732368.4671",
"total_removed": "715655.108029600523393", "total_removed": "715655.108029600523393",
"locked_amount": "218430.066175628697078272948", "locked_amount": "249036.115263241476836903328",
"deposits": [ "deposits": [
{ {
"amount": "1998.95815", "amount": "1998.95815",
@@ -42326,8 +42202,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z", "tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z", "tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001", "total_added": "15870102.715470999700000001",
"total_removed": "871680.07831804700259352", "total_removed": "870712.39693462448509352",
"locked_amount": "6074226.5757489411253544394273819693295485", "locked_amount": "6182753.66071744239145030977234379973803727",
"deposits": [ "deposits": [
{ {
"amount": "16249.93", "amount": "16249.93",
@@ -42831,11 +42707,6 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "967.6813834225175",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x625e0c8fb8d5e2dd7fef0359bb396571f7d8aed05a601ddf0f6e6577edb26a13"
},
{ {
"amount": "10150.87581603206683", "amount": "10150.87581603206683",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D", "user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
@@ -45004,12 +44875,6 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "967.6813834225175",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0x625e0c8fb8d5e2dd7fef0359bb396571f7d8aed05a601ddf0f6e6577edb26a13"
},
{ {
"amount": "913.910324501590625", "amount": "913.910324501590625",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b", "user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -46566,8 +46431,8 @@
} }
], ],
"total_tokens": "259998.8875", "total_tokens": "259998.8875",
"withdrawn_tokens": "159590.522104386423875", "withdrawn_tokens": "158622.840720963906375",
"remaining_tokens": "100408.365395613576125" "remaining_tokens": "101376.046779036093625"
}, },
{ {
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c", "address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
@@ -59069,8 +58934,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z", "tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z", "tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996", "total_added": "472355.6199999996",
"total_removed": "44479.0535455583416", "total_removed": "44110.3307271943416",
"locked_amount": "34611.49258501772999518148756976", "locked_amount": "39461.19601458522069398354033484",
"deposits": [ "deposits": [
{ {
"amount": "3000", "amount": "3000",
@@ -65689,11 +65554,6 @@
} }
], ],
"withdrawals": [ "withdrawals": [
{
"amount": "368.722818364",
"user": "0x0436E2E4EA467f9958403dc56635404EE1bb1E94",
"tx": "0x97f5b5ec5ac839a8d182b9267b5d052cb70f9a18b648874337e3d5360b21a5fb"
},
{ {
"amount": "182.662252664", "amount": "182.662252664",
"user": "0xb1169C6daAc76bAcaf0D8f87641Fc38fbabe569F", "user": "0xb1169C6daAc76bAcaf0D8f87641Fc38fbabe569F",
@@ -81903,17 +81763,10 @@
"tx": "0xb1425d9b0d5f10c5b06236d6f9d61ebddd21c4027e0f41deb5ec227a1753c715" "tx": "0xb1425d9b0d5f10c5b06236d6f9d61ebddd21c4027e0f41deb5ec227a1753c715"
} }
], ],
"withdrawals": [ "withdrawals": [],
{
"amount": "368.722818364",
"user": "0x0436E2E4EA467f9958403dc56635404EE1bb1E94",
"tranche_id": 5,
"tx": "0x97f5b5ec5ac839a8d182b9267b5d052cb70f9a18b648874337e3d5360b21a5fb"
}
],
"total_tokens": "400", "total_tokens": "400",
"withdrawn_tokens": "368.722818364", "withdrawn_tokens": "0",
"remaining_tokens": "31.277181636" "remaining_tokens": "400"
}, },
{ {
"address": "0x6672F8789C54b49C596E6e7Ca3A72726Fc4C400E", "address": "0x6672F8789C54b49C596E6e7Ca3A72726Fc4C400E",
+49 -48
View File
@@ -9,7 +9,6 @@ import {
import { isBefore, isAfter, addSeconds, subSeconds } from 'date-fns'; import { isBefore, isAfter, addSeconds, subSeconds } from 'date-fns';
import { createOrder } from '../support/create-order'; import { createOrder } from '../support/create-order';
import { connectEthereumWallet } from '../support/ethereum-wallet'; import { connectEthereumWallet } from '../support/ethereum-wallet';
import { selectAsset } from '../support/helpers';
const orderSize = 'size'; const orderSize = 'size';
const orderType = 'type'; const orderType = 'type';
@@ -23,13 +22,15 @@ const assetSelectField = 'select[name="asset"]';
const amountField = 'input[name="amount"]'; const amountField = 'input[name="amount"]';
const txTimeout = Cypress.env('txTimeout'); const txTimeout = Cypress.env('txTimeout');
const sepoliaUrl = Cypress.env('ETHERSCAN_URL'); const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
const btcName = 0; const btcName =
const vegaName = 4; 'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC';
const vegaName =
'Vegab4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b - VEGA';
const btcSymbol = 'tBTC'; const btcSymbol = 'tBTC';
const vegaSymbol = 'VEGA'; const vegaSymbol = 'VEGA';
const usdcSymbol = 'fUSDC'; const usdcSymbol = 'fUSDC';
const toastContent = 'toast-content'; const toastContent = 'toast-content';
const openOrdersTab = 'Open'; const ordersTab = 'Orders';
const depositsTab = 'Deposits'; const depositsTab = 'Deposits';
const collateralTab = 'Collateral'; const collateralTab = 'Collateral';
const toastCloseBtn = 'toast-close'; const toastCloseBtn = 'toast-close';
@@ -49,7 +50,6 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.get('@markets').then((markets) => { cy.get('@markets').then((markets) => {
cy.wrap(markets[0]).as('market'); cy.wrap(markets[0]).as('market');
}); });
cy.visit('/#/portfolio');
}); });
beforeEach(() => { beforeEach(() => {
@@ -73,7 +73,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId(depositsTab).click(); cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click(); cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown'); connectEthereumWallet('Unknown');
selectAsset(btcName); cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.getByTestId('approve-default').should( cy.getByTestId('approve-default').should(
'contain.text', 'contain.text',
`Before you can make a deposit of your chosen asset, ${btcSymbol}, you need to approve its use in your Ethereum wallet` `Before you can make a deposit of your chosen asset, ${btcSymbol}, you need to approve its use in your Ethereum wallet`
@@ -81,7 +81,6 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId(approveSubmit).click(); cy.getByTestId(approveSubmit).click();
cy.getByTestId('approve-pending').should('exist'); cy.getByTestId('approve-pending').should('exist');
cy.getByTestId('approve-confirmed').should('exist'); cy.getByTestId('approve-confirmed').should('exist');
cy.get(amountField).focus();
cy.get(amountField).clear().type('10'); cy.get(amountField).clear().type('10');
cy.getByTestId(depositSubmit).click(); cy.getByTestId(depositSubmit).click();
cy.getByTestId(toastContent, txTimeout).should( cy.getByTestId(toastContent, txTimeout).should(
@@ -116,6 +115,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
}); });
it('can key to key transfers', function () { it('can key to key transfers', function () {
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist'); cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(collateralTab).click(); cy.getByTestId(collateralTab).click();
@@ -147,8 +147,8 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId('Withdrawals').click(); cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click(); cy.getByTestId('withdraw-dialog-button').click();
selectAsset(0); connectEthereumWallet('Unknown');
cy.get(amountField).focus(); cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('1'); cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click(); cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should( cy.getByTestId(toastContent, txTimeout).should(
@@ -170,7 +170,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId(toastContent, txTimeout).should( cy.getByTestId(toastContent, txTimeout).should(
'contain.text', 'contain.text',
'Error occurredcannot estimate gas' 'Error occurredprocessing response error'
); );
cy.getByTestId(toastCloseBtn).click({ multiple: true }); cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId(completeWithdrawalBtn).should( cy.getByTestId(completeWithdrawalBtn).should(
@@ -180,7 +180,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
}); });
}); });
describe('capsule', { tags: '@slow', testIsolation: true }, () => { describe('capsule', { tags: '@slow' }, () => {
before(() => { before(() => {
cy.updateCapsuleMultiSig(); cy.updateCapsuleMultiSig();
}); });
@@ -231,7 +231,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.getByTestId(toastContent).should( cy.getByTestId(toastContent).should(
'contain.text', 'contain.text',
`Order submittedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+${order.size} @ ${order.price}.00 ${usdcSymbol}`, `ConfirmedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+${order.size} @ ${order.price}.00 ${usdcSymbol}`,
{ matchCase: false } { matchCase: false }
); );
cy.getByTestId(toastCloseBtn).click(); cy.getByTestId(toastCloseBtn).click();
@@ -242,9 +242,9 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
.get(`[data-testid="bid-vol-${rawPrice}"]`) .get(`[data-testid="bid-vol-${rawPrice}"]`)
.should('contain.text', order.size); .should('contain.text', order.size);
cy.getByTestId(openOrdersTab).click(); cy.getByTestId(ordersTab).click();
cy.getByTestId('edit', txTimeout).should('contain.text', 'Edit'); cy.getByTestId('edit', txTimeout).should('contain.text', 'Edit');
cy.getByTestId('tab-open-orders').within(() => { cy.getByTestId('tab-orders').within(() => {
cy.get('.ag-center-cols-container') cy.get('.ag-center-cols-container')
.children() .children()
.first() .first()
@@ -283,9 +283,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
}); });
}); });
it('can edit order', function () { it('can edit order', function () {
const market = this.market; cy.getByTestId(ordersTab).click();
cy.visit(`/#/markets/${market.id}`);
cy.getByTestId(openOrdersTab).click();
cy.getByTestId('edit').first().should('be.visible').click(); cy.getByTestId('edit').first().should('be.visible').click();
cy.getByTestId('dialog-title').should('contain.text', 'Edit order'); cy.getByTestId('dialog-title').should('contain.text', 'Edit order');
cy.get('#limitPrice').focus().clear().type(newPrice); cy.get('#limitPrice').focus().clear().type(newPrice);
@@ -293,12 +291,12 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.getByTestId(toastContent).should( cy.getByTestId(toastContent).should(
'contain.text', 'contain.text',
`Order submittedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`, `ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false } { matchCase: false }
); );
cy.getByTestId(toastCloseBtn).click({ multiple: true }); cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId(openOrdersTab).click(); cy.getByTestId(ordersTab).click();
cy.get('.ag-center-cols-container') cy.get('.ag-center-cols-container')
.children() .children()
.first() .first()
@@ -309,20 +307,18 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderUpdatedAt); checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderUpdatedAt);
}); });
}); });
// comment because of bug #2695
it('can cancel order', function () { it.skip('can cancel order', function () {
const market = this.market; cy.getByTestId(ordersTab).click();
cy.visit(`/#/markets/${market.id}`);
cy.getByTestId(openOrdersTab).click();
cy.getByTestId('cancel').first().click(); cy.getByTestId('cancel').first().click();
cy.getByTestId(toastContent).should( cy.getByTestId(toastContent).should(
'contain.text', 'contain.text',
`Order cancelledYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+${size} @ ${newPrice}.00 ${usdcSymbol}`, `ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false } { matchCase: false }
); );
cy.getByTestId(toastCloseBtn).click({ multiple: true }); cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId('Closed').click();
cy.getByTestId('tab-closed-orders') cy.getByTestId('tab-orders')
.get('.ag-center-cols-container') .get('.ag-center-cols-container')
.children() .children()
.first() .first()
@@ -352,7 +348,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.getByTestId('Withdrawals').click(); cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click(); cy.getByTestId('withdraw-dialog-button').click();
connectEthereumWallet('Unknown'); connectEthereumWallet('Unknown');
selectAsset(btcName); cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
cy.get(amountField).clear().type('1'); cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click(); cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should( cy.getByTestId(toastContent, txTimeout).should(
@@ -375,15 +371,19 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
'contain.text', 'contain.text',
'Transaction confirmed' 'Transaction confirmed'
); );
cy.getByTestId(toastContent, txTimeout).should( cy.getByTestId(toastCloseBtn).click({ multiple: true });
'contain.text',
'Funds unlockedYour funds have been unlocked for withdrawalView in block explorerWithdraw 1.00 tBTCComplete withdrawal'
);
cy.get('.ag-center-cols-container') cy.wrap(null).then(() => {
.find('[col-id="status"]') try {
.eq(0, txTimeout) cy.getByTestId(completeWithdrawalBtn)
.should('contain.text', 'Completed'); .eq(0, txTimeout)
.should('not.exist');
} catch (error) {
console.log(
'Assertion failed, but we are continuing because this is our wait to complete transaction'
);
}
});
cy.get('[col-id="txHash"]', txTimeout) cy.get('[col-id="txHash"]', txTimeout)
.should('have.length.above', 1) .should('have.length.above', 1)
@@ -404,20 +404,17 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
.should('have.attr', 'href') .should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`); .and('contain', `${sepoliaUrl}/tx/0x`);
}); });
// comment because of bug #2819
cy.getByTestId('withdraw-dialog-button').click({ force: true }); // cy.getByTestId('withdraw-dialog-button').click();
cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '7.999'); // cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '0')
}); });
it('approved amount is less than deposit', function () { it('approved amount is less than deposit', function () {
// 1001-DEPO-006 // 1001-DEPO-006
// 1001-DEPO-007 // 1001-DEPO-007
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(depositsTab).click(); cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click(); cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown'); cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
selectAsset(btcName);
cy.contains('Deposits of tBTC not approved').should('not.exist'); cy.contains('Deposits of tBTC not approved').should('not.exist');
cy.contains('Use maximum').should('be.visible'); cy.contains('Use maximum').should('be.visible');
cy.get(amountField).clear().type('20000000'); cy.get(amountField).clear().type('20000000');
@@ -425,7 +422,11 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.getByTestId(depositSubmit).click(); cy.getByTestId(depositSubmit).click();
cy.getByTestId('input-error-text').should( cy.getByTestId('input-error-text').should(
'contain.text', 'contain.text',
`You can't deposit more than you have in your Ethereum wallet` 'Amount is above approved amount'
);
cy.getByTestId('reapprove-default').should(
'contain.text',
'Approve again to deposit more than'
); );
}); });
@@ -439,11 +440,11 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
cy.getByTestId(depositsTab).click(); cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click(); cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown'); connectEthereumWallet('Unknown');
selectAsset(vegaName); cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
cy.getByTestId('approve-submit').click(); cy.getByTestId('approve-submit').click();
cy.getByTestId('approve-confirmed').should( cy.getByTestId('approve-confirmed').should(
'contain.text', 'contain.text',
'You approved deposits of up to VEGA' 'You can now make deposits in VEGA, up to a maximum of'
); );
cy.get(amountField).clear().type('10000'); cy.get(amountField).clear().type('10000');
cy.getByTestId('deposit-submit').click(); cy.getByTestId('deposit-submit').click();
@@ -477,9 +478,9 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
.and('contain', `${sepoliaUrl}/tx/0x`); .and('contain', `${sepoliaUrl}/tx/0x`);
}); });
cy.getByTestId('Withdrawals').click(txTimeout); cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click(); cy.getByTestId('withdraw-dialog-button').click();
selectAsset(1); cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
cy.get(amountField).clear().type('10000'); cy.get(amountField).clear().type('10000');
cy.getByTestId('DELAY_TIME_value').should('have.text', '5 days'); cy.getByTestId('DELAY_TIME_value').should('have.text', '5 days');
cy.getByTestId('submit-withdrawal').click(); cy.getByTestId('submit-withdrawal').click();
+179 -195
View File
@@ -14,226 +14,210 @@ const itemHeader = 'item-header';
const itemValue = 'item-value'; const itemValue = 'item-value';
const marketListContent = 'popover-content'; const marketListContent = 'popover-content';
describe( describe('Console - market list - live env', { tags: '@live' }, () => {
'Console - market list - live env', beforeEach(() => {
{ tags: '@live', testIsolation: true }, cy.visit('/');
() => { });
beforeEach(() => {
cy.visit('/'); it('shows the market list page', () => {
cy.get('main', { timeout: 20000 });
// Overlay should be shown
cy.getByTestId(selectMarketOverlay).should('exist');
cy.contains('Select a market to get started').should('be.visible');
// I expect the market overlay table to contain at least one row
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.should('have.length.greaterThan', 1);
// each market shown in overlay table contains content under the last price and change fields
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.getByTestId('price')
.should('not.be.empty');
});
it('redirects to a default market', () => {
cy.getByTestId('dialog-close').click();
cy.getByTestId(selectMarketOverlay).should('not.exist');
// the choose market overlay is no longer showing
cy.contains('Select a market to get started').should('not.exist');
cy.contains('Loading...').should('not.exist');
cy.getByTestId('popover-trigger').should('not.be.empty');
});
});
describe('Console - market info - live env', { tags: '@live' }, () => {
before(() => {
cy.visit('/');
cy.contains('Loading market data...').should('not.exist');
cy.getByTestId('link').should('be.visible');
cy.getByTestId('dialog-close').click();
cy.getByTestId(marketInfoBtn).click();
});
const titles = ['Market data', 'Market specification', 'Market governance'];
const subtitles = [
'Current fees',
'Market price',
'Market volume',
'Insurance pool',
'Key details',
'Instrument',
'Settlement asset',
'Metadata',
'Risk model',
'Risk parameters',
'Risk factors',
'Price monitoring bounds 1',
'Liquidity monitoring parameters',
'Liquidity',
'Liquidity price range',
'Oracle',
'Proposal',
];
it('market info titles are displayed', () => {
cy.getByTestId('split-view-view')
.find('.text-lg')
.each((element, index) => {
cy.wrap(element).should('have.text', titles[index]);
});
});
it('market info subtitles are displayed', () => {
cy.getByTestId('popover-trigger').click();
cy.contains('Loading market data...').should('not.exist');
cy.contains('[data-testid="link"]', 'AAVEDAI.MF21').click();
cy.getByTestId(marketInfoBtn).click();
cy.getByTestId(marketInfoSubtitle).each((element, index) => {
cy.wrap(element).should('have.text', subtitles[index]);
}); });
});
it('shows the market list page', () => { it('renders correctly liquidity in trading tab', () => {
cy.get('main', { timeout: 20000 }); cy.getByTestId('Liquidity').click();
cy.contains('Loading').should('not.exist');
// Overlay should be shown cy.contains('Something went wrong').should('not.exist');
cy.getByTestId(selectMarketOverlay).should('exist'); cy.contains('Application error').should('not.exist');
cy.contains('Select a market to get started').should('be.visible'); cy.getByTestId('tab-liquidity').within(() => {
cy.get('[col-id="party.id"]').eq(1).should('not.be.empty');
// I expect the market overlay table to contain at least one row
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.should('have.length.greaterThan', 1);
// each market shown in overlay table contains content under the last price and change fields
cy.getByTestId(selectMarketOverlay)
.get('table tr')
.getByTestId('price')
.should('not.be.empty');
}); });
});
});
it('redirects to a default market', () => { describe('Console - market summary - live env', { tags: '@live' }, () => {
cy.getByTestId('dialog-close').click(); before(() => {
cy.getByTestId(selectMarketOverlay).should('not.exist'); cy.visit('/');
cy.getByTestId('dialog-close').click();
cy.getByTestId(marketSummaryBlock).should('be.visible');
});
// the choose market overlay is no longer showing it('must display market name', () => {
cy.contains('Select a market to get started').should('not.exist'); cy.getByTestId('popover-trigger').should('not.be.empty');
cy.contains('Loading...').should('not.exist'); });
cy.getByTestId('popover-trigger').should('not.be.empty');
});
}
);
describe( it('must see market expiry', () => {
'Console - market info - live env', cy.getByTestId(marketSummaryBlock).within(() => {
{ tags: '@live', testIsolation: true }, cy.getByTestId(marketExpiry).within(() => {
() => { cy.getByTestId(itemHeader).should('have.text', 'Expiry');
before(() => { cy.getByTestId(itemValue).should('not.be.empty');
cy.visit('/');
cy.contains('Loading market data...').should('not.exist');
cy.getByTestId('link').should('be.visible');
cy.getByTestId('dialog-close').click();
cy.getByTestId(marketInfoBtn).click();
});
const titles = ['Market data', 'Market specification', 'Market governance'];
const subtitles = [
'Current fees',
'Market price',
'Market volume',
'Insurance pool',
'Key details',
'Instrument',
'Settlement asset',
'Metadata',
'Risk model',
'Risk parameters',
'Risk factors',
'Price monitoring bounds 1',
'Liquidity monitoring parameters',
'Liquidity',
'Liquidity price range',
'Oracle',
'Proposal',
];
it('market info titles are displayed', () => {
cy.getByTestId('split-view-view')
.find('.text-lg')
.each((element, index) => {
cy.wrap(element).should('have.text', titles[index]);
});
});
it('market info subtitles are displayed', () => {
cy.getByTestId('popover-trigger').click();
cy.contains('Loading market data...').should('not.exist');
cy.contains('[data-testid="link"]', 'AAVEDAI.MF21').click();
cy.getByTestId(marketInfoBtn).click();
cy.getByTestId(marketInfoSubtitle).each((element, index) => {
cy.wrap(element).should('have.text', subtitles[index]);
}); });
}); });
});
it('renders correctly liquidity in trading tab', () => { it('must see market price', () => {
cy.getByTestId('Liquidity').click(); cy.getByTestId(marketSummaryBlock).within(() => {
cy.contains('Loading').should('not.exist'); cy.getByTestId(marketPrice).within(() => {
cy.contains('Something went wrong').should('not.exist'); cy.getByTestId(itemHeader).should('have.text', 'Price');
cy.contains('Application error').should('not.exist'); cy.getByTestId(itemValue).should('not.be.empty');
cy.getByTestId('tab-liquidity').within(() => {
cy.get('[col-id="party.id"]').eq(1).should('not.be.empty');
}); });
}); });
} });
);
describe( it('must see market change', () => {
'Console - market summary - live env', cy.getByTestId(marketSummaryBlock).within(() => {
{ tags: '@live', testIsolation: true }, cy.getByTestId(marketChange).within(() => {
() => { cy.getByTestId(itemHeader).should('have.text', 'Change (24h)');
before(() => { cy.getByTestId(percentageValue).should('not.be.empty');
cy.visit('/'); cy.getByTestId(priceChangeValue).should('not.be.empty');
cy.getByTestId('dialog-close').click();
cy.getByTestId(marketSummaryBlock).should('be.visible');
});
it('must display market name', () => {
cy.getByTestId('popover-trigger').should('not.be.empty');
});
it('must see market expiry', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketExpiry).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Expiry');
cy.getByTestId(itemValue).should('not.be.empty');
});
}); });
}); });
});
it('must see market price', () => { it('must see market volume', () => {
cy.getByTestId(marketSummaryBlock).within(() => { cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketPrice).within(() => { cy.getByTestId(marketVolume).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Price'); cy.getByTestId(itemHeader).should('have.text', 'Volume (24h)');
cy.getByTestId(itemValue).should('not.be.empty'); cy.getByTestId(itemValue).should('not.be.empty');
});
}); });
}); });
});
it('must see market change', () => { it('must see market mode', () => {
cy.getByTestId(marketSummaryBlock).within(() => { cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketChange).within(() => { cy.getByTestId(marketMode).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Change (24h)'); cy.getByTestId(itemHeader).should('have.text', 'Trading mode');
cy.getByTestId(percentageValue).should('not.be.empty'); cy.getByTestId(itemValue).should('not.be.empty');
cy.getByTestId(priceChangeValue).should('not.be.empty');
});
}); });
}); });
});
it('must see market volume', () => { it('must see market settlement', () => {
cy.getByTestId(marketSummaryBlock).within(() => { cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketVolume).within(() => { cy.getByTestId(marketSettlement).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Volume (24h)'); cy.getByTestId(itemHeader).should('have.text', 'Settlement asset');
cy.getByTestId(itemValue).should('not.be.empty'); cy.getByTestId(itemValue).should('not.be.empty');
});
}); });
}); });
});
});
it('must see market mode', () => { describe('Console - markets table - live env', { tags: '@live' }, () => {
cy.getByTestId(marketSummaryBlock).within(() => { beforeEach(() => {
cy.getByTestId(marketMode).within(() => { cy.visit('/');
cy.getByTestId(itemHeader).should('have.text', 'Trading mode'); });
cy.getByTestId(itemValue).should('not.be.empty');
}); it('renders markets correctly', () => {
cy.get('[data-testid^="market-link-"]').should('not.be.empty');
cy.getByTestId('price').invoke('text').should('not.be.empty');
cy.getByTestId('settlement-asset').should('not.be.empty');
cy.getByTestId('price-change-percentage').should('not.be.empty');
cy.getByTestId('price-change').should('not.be.empty');
cy.getByTestId('sparkline-svg').should('be.visible');
});
it('renders market list drop down', () => {
openMarketDropDown();
cy.getByTestId(marketListContent)
.find('[data-testid="price"]')
.invoke('text')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="trading-mode-col"]')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="taker-fee"]')
.should('contain.text', '%');
cy.getByTestId(marketListContent)
.find('[data-testid="market-volume"]')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="market-name"]')
.should('not.be.empty');
});
it('Able to select market from dropdown', () => {
cy.getByTestId('popover-trigger')
.invoke('text')
.then((marketName) => {
openMarketDropDown();
cy.get('[data-testid^=market-link]').eq(1).click();
cy.getByTestId('popover-trigger').should('not.be.equal', marketName);
}); });
}); });
});
it('must see market settlement', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketSettlement).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Settlement asset');
cy.getByTestId(itemValue).should('not.be.empty');
});
});
});
}
);
describe(
'Console - markets table - live env',
{ tags: '@live', testIsolation: true },
() => {
beforeEach(() => {
cy.visit('/');
});
it('renders markets correctly', () => {
cy.get('[data-testid^="market-link-"]').should('not.be.empty');
cy.getByTestId('price').invoke('text').should('not.be.empty');
cy.getByTestId('settlement-asset').should('not.be.empty');
cy.getByTestId('price-change-percentage').should('not.be.empty');
cy.getByTestId('price-change').should('not.be.empty');
cy.getByTestId('sparkline-svg').should('be.visible');
});
it('renders market list drop down', () => {
openMarketDropDown();
cy.getByTestId(marketListContent)
.find('[data-testid="price"]')
.invoke('text')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="trading-mode-col"]')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="taker-fee"]')
.should('contain.text', '%');
cy.getByTestId(marketListContent)
.find('[data-testid="market-volume"]')
.should('not.be.empty');
cy.getByTestId(marketListContent)
.find('[data-testid="market-name"]')
.should('not.be.empty');
});
it('Able to select market from dropdown', () => {
cy.getByTestId('popover-trigger')
.invoke('text')
.then((marketName) => {
openMarketDropDown();
cy.get('[data-testid^=market-link]').eq(1).click();
cy.getByTestId('popover-trigger').should('not.be.equal', marketName);
});
});
}
);
function openMarketDropDown() { function openMarketDropDown() {
cy.contains('Loading...').should('not.exist'); cy.contains('Loading...').should('not.exist');
@@ -12,7 +12,7 @@ import {
formatNumberPercentage, formatNumberPercentage,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { updateGridData } from '@vegaprotocol/datagrid'; import { updateGridData } from '@vegaprotocol/react-helpers';
import { import {
NetworkParams, NetworkParams,
useNetworkParams, useNetworkParams,
@@ -155,7 +155,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick} onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder enforceBottomPlaceholder
storeKey="marketOpenOrders"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -167,7 +166,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick} onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder enforceBottomPlaceholder
storeKey="marketClosedOrders"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -179,7 +177,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick} onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder enforceBottomPlaceholder
storeKey="marketRejectOrders"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -190,7 +187,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick} onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder enforceBottomPlaceholder
storeKey="marketAllOrders"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -199,7 +195,6 @@ const MarketBottomPanel = memo(
<TradingViews.fills.component <TradingViews.fills.component
marketId={marketId} marketId={marketId}
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
storeKey="marketFills"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -218,7 +213,6 @@ const MarketBottomPanel = memo(
<TradingViews.positions.component <TradingViews.positions.component
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
noBottomPlaceholder noBottomPlaceholder
storeKey="marketPositions"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -228,7 +222,6 @@ const MarketBottomPanel = memo(
pinnedAsset={pinnedAsset} pinnedAsset={pinnedAsset}
noBottomPlaceholder noBottomPlaceholder
hideButtons hideButtons
storeKey="marketCollateral"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -241,10 +234,7 @@ const MarketBottomPanel = memo(
<Tabs storageKey="console-trade-grid-bottom"> <Tabs storageKey="console-trade-grid-bottom">
<Tab id="positions" name={t('Positions')}> <Tab id="positions" name={t('Positions')}>
<VegaWalletContainer> <VegaWalletContainer>
<TradingViews.positions.component <TradingViews.positions.component onMarketClick={onMarketClick} />
onMarketClick={onMarketClick}
storeKey="marketPositions"
/>
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
<Tab id="open-orders" name={t('Open')}> <Tab id="open-orders" name={t('Open')}>
@@ -255,7 +245,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick} onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder enforceBottomPlaceholder
storeKey="marketOpenOrders"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -267,7 +256,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick} onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder enforceBottomPlaceholder
storeKey="marketClosedOrders"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -279,7 +267,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick} onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder enforceBottomPlaceholder
storeKey="marketRejectedOrders"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -290,7 +277,6 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick} onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder enforceBottomPlaceholder
storeKey="marketAllOrders"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -299,7 +285,6 @@ const MarketBottomPanel = memo(
<TradingViews.fills.component <TradingViews.fills.component
marketId={marketId} marketId={marketId}
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
storeKey="marketFills"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -308,7 +293,6 @@ const MarketBottomPanel = memo(
<TradingViews.collateral.component <TradingViews.collateral.component
pinnedAsset={pinnedAsset} pinnedAsset={pinnedAsset}
hideButtons hideButtons
storeKey="marketCollateral"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
+1 -2
View File
@@ -264,7 +264,6 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
{ {
headerName: t('Market ID'), headerName: t('Market ID'),
field: 'id', field: 'id',
flex: 1,
}, },
]; ];
return cols; return cols;
@@ -277,10 +276,10 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
columnDefs={colDefs} columnDefs={colDefs}
getRowId={({ data }) => data.id} getRowId={({ data }) => data.id}
defaultColDef={{ defaultColDef={{
flex: 1,
resizable: true, resizable: true,
}} }}
overlayNoRowsTemplate="No data" overlayNoRowsTemplate="No data"
storeKey="closedMarkets"
/> />
); );
}; };
@@ -2,7 +2,7 @@ import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits'; import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits'; import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid'; import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { useRef } from 'react'; import { useRef } from 'react';
@@ -53,7 +53,6 @@ export const Portfolio = () => {
<PositionsContainer <PositionsContainer
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
noBottomPlaceholder noBottomPlaceholder
storeKey="portfolioPositions"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
@@ -62,16 +61,12 @@ export const Portfolio = () => {
<OrderListContainer <OrderListContainer
onMarketClick={onMarketClick} onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick} onOrderTypeClick={onOrderTypeClick}
storeKey="portfolioOrders"
/> />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
<Tab id="fills" name={t('Fills')}> <Tab id="fills" name={t('Fills')}>
<VegaWalletContainer> <VegaWalletContainer>
<FillsContainer <FillsContainer onMarketClick={onMarketClick} />
onMarketClick={onMarketClick}
storeKey="portfolioFills"
/>
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
<Tab id="ledger-entries" name={t('Ledger entries')}> <Tab id="ledger-entries" name={t('Ledger entries')}>
@@ -91,7 +86,7 @@ export const Portfolio = () => {
<Tabs storageKey="console-portfolio-bottom"> <Tabs storageKey="console-portfolio-bottom">
<Tab id="collateral" name={t('Collateral')}> <Tab id="collateral" name={t('Collateral')}>
<VegaWalletContainer> <VegaWalletContainer>
<AccountsContainer storeKey="portfolioCollateral" /> <AccountsContainer />
</VegaWalletContainer> </VegaWalletContainer>
</Tab> </Tab>
<Tab id="deposits" name={t('Deposits')}> <Tab id="deposits" name={t('Deposits')}>
@@ -13,12 +13,10 @@ export const AccountsContainer = ({
pinnedAsset, pinnedAsset,
hideButtons, hideButtons,
noBottomPlaceholder, noBottomPlaceholder,
storeKey,
}: { }: {
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
hideButtons?: boolean; hideButtons?: boolean;
noBottomPlaceholder?: boolean; noBottomPlaceholder?: boolean;
storeKey?: string;
}) => { }) => {
const { pubKey, isReadOnly } = useVegaWallet(); const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
@@ -51,7 +49,6 @@ export const AccountsContainer = ({
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset} pinnedAsset={pinnedAsset}
noBottomPlaceholder={noBottomPlaceholder} noBottomPlaceholder={noBottomPlaceholder}
storeKey={storeKey}
/> />
{!isReadOnly && !hideButtons && ( {!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded"> <div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
-11
View File
@@ -93,11 +93,6 @@ html [data-theme='light'] {
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);
@@ -121,9 +116,3 @@ html [data-theme='light'] {
--ag-row-hover-color: theme(colors.neutral[800]); --ag-row-hover-color: theme(colors.neutral[800]);
--ag-font-size: 12px; --ag-font-size: 12px;
} }
.ag-theme-balham-dark .ag-row.no-hover,
.ag-theme-balham-dark .ag-row.no-hover:hover,
.ag-theme-balham .ag-row.no-hover,
.ag-theme-balham .ag-row.no-hover:hover {
background: var(--ag-background-color);
}
+1 -4
View File
@@ -1,6 +1,6 @@
import { useRef, useMemo, memo, useCallback } from 'react'; import { useRef, useMemo, memo, useCallback } from 'react';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid'; import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/data-provider';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
@@ -17,7 +17,6 @@ interface AccountManagerProps {
isReadOnly: boolean; isReadOnly: boolean;
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
noBottomPlaceholder?: boolean; noBottomPlaceholder?: boolean;
storeKey?: string;
} }
export const AccountManager = ({ export const AccountManager = ({
@@ -28,7 +27,6 @@ export const AccountManager = ({
isReadOnly, isReadOnly,
pinnedAsset, pinnedAsset,
noBottomPlaceholder, noBottomPlaceholder,
storeKey,
}: AccountManagerProps) => { }: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]); const variables = useMemo(() => ({ partyId }), [partyId]);
@@ -61,7 +59,6 @@ export const AccountManager = ({
suppressLoadingOverlay suppressLoadingOverlay
suppressNoRowsOverlay suppressNoRowsOverlay
pinnedAsset={pinnedAsset} pinnedAsset={pinnedAsset}
storeKey={storeKey}
{...bottomPlaceholderProps} {...bottomPlaceholderProps}
/> />
<div className="pointer-events-none absolute inset-0"> <div className="pointer-events-none absolute inset-0">
+2 -2
View File
@@ -100,7 +100,6 @@ export interface AccountTableProps extends AgGridReactProps {
onClickDeposit?: (assetId: string) => void; onClickDeposit?: (assetId: string) => void;
isReadOnly: boolean; isReadOnly: boolean;
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
storeKey?: string;
} }
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>( export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
@@ -156,6 +155,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
(data) => data.asset.id !== pinnedAssetId (data) => data.asset.id !== pinnedAssetId
)} )}
defaultColDef={{ defaultColDef={{
flex: 1,
resizable: true, resizable: true,
tooltipComponent: TooltipCellComponent, tooltipComponent: TooltipCellComponent,
sortable: true, sortable: true,
@@ -187,6 +187,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
</ButtonLink> </ButtonLink>
); );
}} }}
maxWidth={300}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Used')} headerName={t('Used')}
@@ -361,7 +362,6 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
); );
} }
}} }}
flex={1}
/> />
} }
</AgGrid> </AgGrid>
@@ -9,8 +9,9 @@ declare global {
} }
export function addGetNetworkParameters() { export function addGetNetworkParameters() {
// @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command
Cypress.Commands.add('get_network_parameters', () => { Cypress.Commands.add('get_network_parameters', () => {
const query = ` const mutation = `
{ {
networkParametersConnection { networkParametersConnection {
edges { edges {
@@ -25,17 +26,17 @@ export function addGetNetworkParameters() {
method: 'POST', method: 'POST',
url: `http://localhost:3008/graphql`, url: `http://localhost:3008/graphql`,
body: { body: {
query, query: mutation,
}, },
headers: { 'content-type': 'application/json' }, headers: { 'content-type': 'application/json' },
}) })
.its('body.data.networkParametersConnection.edges') .its('body.data.networkParametersConnection.edges')
.then(function (response) { .then(function (response) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command
const object = response.reduce(function (obj: any, edge: any) { const object = response.reduce(function (r, e) {
const { value, key } = edge.node; const { value, key } = e.node;
obj[key] = value; r[key] = value;
return obj; return r;
}, {}); }, {});
return cy.wrap(object); return cy.wrap(object);
}); });
-5
View File
@@ -1,5 +1,4 @@
export * from './lib/ag-grid/ag-grid-lazy'; export * from './lib/ag-grid/ag-grid-lazy';
export * from './lib/ag-grid/use-column-sizes';
export * from './lib/cells/cumulative-vol-cell'; export * from './lib/cells/cumulative-vol-cell';
export * from './lib/cells/flash-cell'; export * from './lib/cells/flash-cell';
@@ -11,7 +10,6 @@ export * from './lib/cells/vol-cell';
export * from './lib/cells/centered-grid-cell'; export * from './lib/cells/centered-grid-cell';
export * from './lib/cells/market-name-cell'; export * from './lib/cells/market-name-cell';
export * from './lib/cells/order-type-cell'; export * from './lib/cells/order-type-cell';
export * from './lib/cells/size';
export * from './lib/filters/date-range-filter'; export * from './lib/filters/date-range-filter';
export * from './lib/filters/set-filter'; export * from './lib/filters/set-filter';
@@ -20,6 +18,3 @@ export * from './lib/cell-class-rules';
export * from './lib/type-helpers'; export * from './lib/type-helpers';
export * from './lib/cells/grid-progress-bar'; export * from './lib/cells/grid-progress-bar';
export * from './lib/ag-grid-update';
export * from './lib/use-bottom-placeholder';
@@ -1,30 +1,22 @@
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react'; import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { AgGridReact } from 'ag-grid-react'; import { AgGridReact } from 'ag-grid-react';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers'; import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { useColumnSizes } from './use-column-sizes';
import classNames from 'classnames'; import classNames from 'classnames';
export const AgGridThemed = ({ export const AgGridThemed = ({
style, style,
gridRef, gridRef,
storeKey,
...props ...props
}: (AgGridReactProps | AgReactUiProps) & { }: (AgGridReactProps | AgReactUiProps) & {
style?: React.CSSProperties; style?: React.CSSProperties;
gridRef?: React.ForwardedRef<AgGridReact>; gridRef?: React.ForwardedRef<AgGridReact>;
storeKey?: string;
}) => { }) => {
const commonColumnCallbacks = useColumnSizes({
storeKey,
props,
});
const { theme } = useThemeSwitcher(); const { theme } = useThemeSwitcher();
const defaultProps = { const defaultProps = {
rowHeight: 22, rowHeight: 22,
headerHeight: 22, headerHeight: 22,
enableCellTextSelection: true, enableCellTextSelection: true,
}; };
const wrapperClasses = classNames('vega-ag-grid', { const wrapperClasses = classNames('vega-ag-grid', {
'ag-theme-balham': theme === 'light', 'ag-theme-balham': theme === 'light',
'ag-theme-balham-dark': theme === 'dark', 'ag-theme-balham-dark': theme === 'dark',
@@ -32,12 +24,7 @@ export const AgGridThemed = ({
return ( return (
<div className={wrapperClasses} style={style}> <div className={wrapperClasses} style={style}>
<AgGridReact <AgGridReact {...defaultProps} {...props} ref={gridRef} />
{...defaultProps}
{...props}
{...commonColumnCallbacks}
ref={gridRef}
/>
</div> </div>
); );
}; };
@@ -4,7 +4,6 @@ import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
type Props = AgGridReactProps & { type Props = AgGridReactProps & {
style?: React.CSSProperties; style?: React.CSSProperties;
gridRef?: React.Ref<AgGridReact>; gridRef?: React.Ref<AgGridReact>;
storeKey?: string;
}; };
export const AgGridLazyInternal = lazy(() => export const AgGridLazyInternal = lazy(() =>
@@ -1,117 +0,0 @@
import type {
Column,
ColumnResizedEvent,
GridSizeChangedEvent,
GridReadyEvent,
} from 'ag-grid-community';
import { renderHook, act, waitFor } from '@testing-library/react';
import { useColumnSizes } from './use-column-sizes';
const mockApis = {
api: {
sizeColumnsToFit: jest.fn(),
},
columnApi: {
setColumnWidths: jest.fn(),
},
};
const mockValueSetter = jest.fn();
const mockStore = {
sizes: { testid: { col1: 100 } },
valueSetter: mockValueSetter,
};
jest.mock('zustand', () => ({
...jest.requireActual('zustand'),
create: () =>
jest.fn(() =>
jest.fn().mockImplementation((creator) => {
return creator(mockStore);
})
),
}));
describe('UseColumnSizes hook', () => {
const storeKey = 'testid';
beforeEach(() => {
jest.clearAllMocks();
});
it('should return proper methods', () => {
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
expect(Object.keys(result.current)).toHaveLength(3);
expect(result.current).toStrictEqual({
onColumnResized: expect.any(Function),
onGridReady: expect.any(Function),
onGridSizeChanged: expect.any(Function),
});
});
it('onGridSizeChanged should call setSize', async () => {
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
await act(() => {
result.current.onGridSizeChanged?.({
clientWidth: 1000,
...mockApis,
} as GridSizeChangedEvent);
});
await waitFor(() => {
expect(mockApis.columnApi.setColumnWidths).toHaveBeenCalledWith([
{ key: 'col1', newWidth: 100 },
]);
});
});
it('onColumnResized should fill up store', async () => {
const columns: Column[] = [
{ getColId: () => 'col1', getActualWidth: () => 100 },
{ getColId: () => 'col2', getActualWidth: () => 200 },
] as Column[];
const sizeObj = { col1: 100, col2: 200, clientWidth: 1000 };
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
await act(() => {
result.current.onGridSizeChanged?.({
clientWidth: 1000,
...mockApis,
} as GridSizeChangedEvent);
});
await act(() => {
result.current.onColumnResized?.({
columns,
finished: true,
source: 'uiColumnDragged',
...mockApis,
} as ColumnResizedEvent);
});
await waitFor(() => {
expect(mockValueSetter).toHaveBeenCalledWith(storeKey, sizeObj);
});
});
it('onGridReady should call setSizes', async () => {
const props = { onGridReady: jest.fn() };
const { result } = renderHook(() => useColumnSizes({ storeKey, props }));
const obTest = { cool: 1, ...mockApis };
await act(() => {
result.current.onGridReady?.(obTest as GridReadyEvent);
});
expect(props.onGridReady).toHaveBeenCalledWith(obTest);
expect(mockApis.api.sizeColumnsToFit).toHaveBeenCalledWith();
});
it('if no storeKey should be transparent', () => {
const { result } = renderHook(() =>
useColumnSizes({ storeKey: '', props: {} })
);
expect(result.current).toStrictEqual({
onColumnResized: undefined,
onGridReady: undefined,
onGridSizeChanged: undefined,
});
});
});
@@ -1,143 +0,0 @@
import { useCallback, useRef } from 'react';
import type {
GridSizeChangedEvent,
GridReadyEvent,
ColumnResizedEvent,
} from 'ag-grid-community';
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
const STORAGE_KEY = 'vega_columns_sizes_store';
export const useColumnSizesStore = create<{
sizes: Record<string, Record<string, number>>;
valueSetter: (storeKey: string, value: Record<string, number>) => void;
}>()(
persist(
immer((set) => ({
sizes: {},
valueSetter: (storeKey, value) =>
set((state) => {
state.sizes[storeKey] = {
...(state.sizes[storeKey] || {}),
...value,
};
return state;
}),
})),
{ name: STORAGE_KEY }
)
);
interface UseColumnSizesProps {
props: AgGridReactProps | AgReactUiProps;
storeKey?: string;
}
export const useColumnSizes = ({
storeKey = '',
props,
}: UseColumnSizesProps): {
onColumnResized?: (event: ColumnResizedEvent) => void;
onGridReady?: (event: GridReadyEvent) => void;
onGridSizeChanged?: (event: GridSizeChangedEvent) => void;
} => {
const sizes = useColumnSizesStore((store) => store.sizes[storeKey] || {});
const valueSetter = useColumnSizesStore((store) => store.valueSetter);
const widthRef = useRef(sizes['clientWidth'] || 0);
const {
onColumnResized: parentOnColumnResized,
onGridReady: parentOnGridReady,
onGridSizeChanged: parentOnGridSizeChanged,
} = props;
const recalculateSizes = useCallback((sizes: Record<string, number>) => {
if (
widthRef.current &&
sizes['clientWidth'] &&
widthRef.current !== sizes['clientWidth']
) {
const oldWidth = sizes['clientWidth'];
const ratio = widthRef.current / oldWidth;
return {
...Object.entries(sizes).reduce((agg, [key, value]) => {
agg[key] = value * ratio;
return agg;
}, {} as Record<string, number>),
width: widthRef.current,
} as Record<string, number>;
}
return sizes;
}, []);
const onColumnResized = useCallback(
(event: ColumnResizedEvent) => {
parentOnColumnResized?.(event);
if (
storeKey &&
event.source === 'uiColumnDragged' &&
event.finished &&
widthRef.current
) {
const { columns } = event;
if (columns?.length) {
const sizesObj = columns.reduce((aggr, column) => {
aggr[column.getColId()] = column.getActualWidth();
return aggr;
}, {} as Record<string, number>);
sizesObj['clientWidth'] = widthRef.current;
valueSetter(storeKey, sizesObj);
}
}
},
[valueSetter, storeKey, parentOnColumnResized]
);
const setSizes = useCallback(
(apiEvent: GridReadyEvent | GridSizeChangedEvent) => {
if (!storeKey || !Object.keys(sizes).length || !widthRef.current) {
apiEvent.api.sizeColumnsToFit();
} else {
const recalculatedSizes = recalculateSizes(sizes);
const newSizes = Object.entries(recalculatedSizes).map(
([key, size]) => ({
key,
newWidth: size,
})
);
apiEvent.columnApi.setColumnWidths(newSizes);
}
},
[storeKey, recalculateSizes, sizes]
);
const onGridReady = useCallback(
(event: GridReadyEvent) => {
parentOnGridReady?.(event);
setSizes(event);
},
[setSizes, parentOnGridReady]
);
const onGridSizeChanged = useCallback(
(event: GridSizeChangedEvent) => {
parentOnGridSizeChanged?.(event);
widthRef.current = event.clientWidth;
setSizes(event);
},
[parentOnGridSizeChanged, setSizes]
);
if (storeKey) {
return {
onGridReady,
onGridSizeChanged,
onColumnResized,
};
}
return {
onGridReady: parentOnGridReady,
onGridSizeChanged: parentOnGridSizeChanged,
onColumnResized: parentOnColumnResized,
};
};
-3
View File
@@ -1,4 +1 @@
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import ResizeObserver from 'resize-observer-polyfill';
global.ResizeObserver = ResizeObserver;
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { VegaWalletContext } from '@vegaprotocol/wallet'; import { VegaWalletContext } from '@vegaprotocol/wallet';
import { act, render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { generateMarket, generateMarketData } from '../../test-helpers'; import { generateMarket, generateMarketData } from '../../test-helpers';
import { DealTicket } from './deal-ticket'; import { DealTicket } from './deal-ticket';
@@ -40,21 +40,12 @@ describe('DealTicket', () => {
}); });
it('should display ticket defaults', () => { it('should display ticket defaults', () => {
const { container } = render(generateJsx()); render(generateJsx());
// Assert defaults are used // Assert defaults are used
expect( expect(
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`) screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`)
).toBeInTheDocument(); ).toBeInTheDocument();
expect(
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_LIMIT}`)
).toBeInTheDocument();
const oderTypeLimitToggle = container.querySelector(
`[data-testid="order-type-${Schema.OrderType.TYPE_LIMIT}"] input[type="radio"]`
);
expect(oderTypeLimitToggle).toBeChecked();
expect( expect(
screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input') screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input')
).toBeChecked(); ).toBeChecked();
@@ -63,15 +54,8 @@ describe('DealTicket', () => {
).not.toBeChecked(); ).not.toBeChecked();
expect(screen.getByTestId('order-size')).toHaveDisplayValue('0'); expect(screen.getByTestId('order-size')).toHaveDisplayValue('0');
expect(screen.getByTestId('order-tif')).toHaveValue( expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_GTC Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
); );
});
it('should display last price for market type order', () => {
render(generateJsx());
act(() => {
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`).click();
});
// Assert last price is shown // Assert last price is shown
expect(screen.getByTestId('last-price')).toHaveTextContent( expect(screen.getByTestId('last-price')).toHaveTextContent(
// eslint-disable-next-line // eslint-disable-next-line
@@ -222,11 +206,7 @@ describe('DealTicket', () => {
it('handles TIF select box dependent on order type', async () => { it('handles TIF select box dependent on order type', async () => {
render(generateJsx()); render(generateJsx());
act(() => { // Only FOK and IOC should be present by default (type market order)
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`).click();
});
// Only FOK and IOC should be present for type market order
expect( expect(
Array.from(screen.getByTestId('order-tif').children).map( Array.from(screen.getByTestId('order-tif').children).map(
(o) => o.textContent (o) => o.textContent
@@ -20,8 +20,8 @@ interface TypeSelectorProps {
} }
const toggles = [ const toggles = [
{ label: t('Limit'), value: Schema.OrderType.TYPE_LIMIT },
{ label: t('Market'), value: Schema.OrderType.TYPE_MARKET }, { label: t('Market'), value: Schema.OrderType.TYPE_MARKET },
{ label: t('Limit'), value: Schema.OrderType.TYPE_LIMIT },
]; ];
export const TypeSelector = ({ export const TypeSelector = ({
+1 -3
View File
@@ -26,10 +26,9 @@ export const DepositsTable = forwardRef<
<AgGrid <AgGrid
ref={ref} ref={ref}
overlayNoRowsTemplate={t('No deposits')} overlayNoRowsTemplate={t('No deposits')}
defaultColDef={{ resizable: true }} defaultColDef={{ flex: 1, resizable: true }}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
suppressCellFocus={true} suppressCellFocus={true}
storeKey="depositTable"
{...props} {...props}
> >
<AgGridColumn headerName="Asset" field="asset.symbol" /> <AgGridColumn headerName="Asset" field="asset.symbol" />
@@ -81,7 +80,6 @@ export const DepositsTable = forwardRef<
</EtherscanLink> </EtherscanLink>
); );
}} }}
flex={1}
/> />
</AgGrid> </AgGrid>
); );
+15 -12
View File
@@ -34,22 +34,25 @@ fragment FillEdge on TradeEdge {
cursor cursor
} }
query Fills($filter: TradesFilter, $pagination: Pagination) { query Fills($partyId: ID!, $marketId: ID, $pagination: Pagination) {
trades(filter: $filter, pagination: $pagination) { party(id: $partyId) {
edges { id
...FillEdge tradesConnection(marketId: $marketId, pagination: $pagination) {
} edges {
pageInfo { ...FillEdge
startCursor }
endCursor pageInfo {
hasNextPage startCursor
hasPreviousPage endCursor
hasNextPage
hasPreviousPage
}
} }
} }
} }
subscription FillsEvent($filter: TradesSubscriptionFilter!) { subscription FillsEvent($partyId: ID!) {
tradesStream(filter: $filter) { trades(partyId: $partyId) {
id id
marketId marketId
buyOrder buyOrder

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