Compare commits

..
Author SHA1 Message Date
Dexter cde6577356 chore(explorer,trading,governance): change explorer api nodes 2023-05-09 13:07:15 +01:00
Dexter b41de05cfc chore(explorer,trading,governance): change data node URLs to new ones 2023-05-09 12:59:58 +01:00
m.ray 00679c75f2 fix(toasts): proposal toasts timeout (#3645) 2023-05-09 10:03:17 +00:00
Art 7fe722dad0 fix(explorer): use data provider import (#3648) 2023-05-09 09:46:24 +00:00
Maciek 0d8a74dfd0 fix(trading): wrong hover effect on datagrid (#3647) 2023-05-09 09:26:59 +00:00
Edd 928b899c46 fix(explorer): improve party page loading (#3274) 2023-05-09 09:43:12 +01:00
Bartłomiej Głownia 72d93bb568 feat(trading): update fills queries, fix empty fills list issue (#3636) 2023-05-09 08:40:06 +00:00
Matthew Russell db75261cd5 chore(react-helpers): remove ag-grid deps (#3628) 2023-05-09 09:04:33 +01:00
Joe Tsang 66fbe28cf0 test(governance): e2e test to include withdrawal toasts (#3624) 2023-05-09 09:03:45 +01:00
Edd 9f3b8ecade fix(explorer): set explorer url env var in explorer (#3622) 2023-05-09 09:03:35 +01:00
Matthew Russell cd13c61e90 fix(datagrid): remove default balham borders (#3642) 2023-05-09 09:02:39 +01:00
Maciek 246577f401 feat(trading): persist table column sizes (#3595) 2023-05-09 08:09:53 +02:00
dexturr 503ca26f68 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-09 06:07:05 +00:00
dexturr 13e374a6bb chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-09 00:15:01 +00:00
Art bed411040c chore(trading): make limit default order type (#3619) 2023-05-08 15:48:43 -07:00
Joe Tsang d0173f15b2 chore(governance): assert proposal dates (#3610) 2023-05-08 15:48:14 -07:00
Matthew Russell 106b040977 chore(utils): remove ethers deps from ui-toolkit (#3627) 2023-05-08 14:40:31 -07:00
dexturr bf84b04a30 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-08 18:07:04 +00:00
Matthew Russell 9f442698a4 fix(explorer): network params fixture lookup for updateAsset proposals (#3633) 2023-05-08 17:29:29 +01:00
daro-maj d1265a6af7 test(trading): update of capsule and live tests (#3635) 2023-05-08 16:52:33 +02:00
Sam Keen 313eff1c95 feat(governance): proposal details page improvements (#3611) 2023-05-08 12:41:55 +00:00
dexturr 7f949a276c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-08 12:10:52 +00:00
dexturr 76a53dc7e3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-08 06:08:30 +00:00
Matthew Russell 72821dd183 chore(utils,react-helpers,data-provider): remove apollo deps from ui-toolkit (#3626) 2023-05-07 18:01:07 -07:00
dexturr 40d2e033e0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-08 00:15:27 +00:00
Edd e6d0b6ac1f fix(explorer): number formatting for updateasset proposals (#3630) 2023-05-07 18:17:57 +00:00
dexturr 99161497fe chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-07 18:08:18 +00:00
Edd bf6ab32230 feat(governance): render markdown in proposal view (#3632) 2023-05-07 17:45:19 +01:00
dexturr 28bcb4ada1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-07 12:10:24 +00:00
dexturr 5812c8858d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-07 06:07:03 +00:00
dexturr abf146895d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-07 00:16:07 +00:00
dexturr 4476e6c6d1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-06 18:09:41 +00:00
dexturr d967000e8f chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-06 12:09:52 +00:00
dexturr 91d3d97399 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-06 06:07:05 +00:00
dexturr ca700aa103 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-06 00:14:21 +00:00
dexturr 265bf7fb35 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-05-05 18:08:29 +00:00
151 changed files with 2316 additions and 1559 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
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_WEBSOCKET_URL=wss://mainnet-observer-proxy01.ops.vega.xyz/websocket
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest
@@ -10,6 +10,8 @@
"governance.proposal.updateMarket.minVoterBalance",
"governance.proposal.updateNetParam.minProposerBalance",
"governance.proposal.updateNetParam.minVoterBalance",
"governance.proposal.updateAsset.minProposerBalance",
"governance.proposal.updateAsset.minVoterBalance",
"reward.staking.delegation.maxPayoutPerEpoch",
"reward.staking.delegation.maxPayoutPerParticipant",
"reward.staking.delegation.minimumValidatorStake",
@@ -19,9 +21,6 @@
"validators.delegation.minAmount"
],
"fiveDecimal": [
"governance.proposal.updateAsset.minProposerBalance",
"governance.proposal.updateAsset.minVoterBalance",
"governance.proposal.updateAsset.requiredParticipation",
"market.fee.factors.infrastructureFee",
"market.fee.factors.makerFee",
"market.liquidity.bondPenaltyParameter",
@@ -77,6 +76,7 @@
"governance.proposal.updateNetParam.requiredMajority",
"governance.proposal.updateNetParam.requiredParticipation",
"governance.proposal.updateMarket.minProposerEquityLikeShare",
"governance.proposal.updateAsset.requiredParticipation",
"validators.vote.required"
],
"duration": [
@@ -138,7 +138,7 @@ context('Network parameters page', { tags: '@smoke' }, function () {
});
});
it('should list each network parameter displayed as a currency value with four decimals - in the correct format', function () {
it.skip('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) => {
network_parameters = Object.entries(network_parameters);
network_parameters.forEach((network_parameter) => {
+1
View File
@@ -4,6 +4,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
NX_VEGA_ENV=CUSTOM
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
# App flags
NX_EXPLORER_TXS_LIST=0
+2 -1
View File
@@ -8,4 +8,5 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
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_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=/
+5 -4
View File
@@ -1,9 +1,10 @@
# App configuration variables
NX_TENDERMINT_URL=https://be.explorer.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz/websocket
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
NX_BLOCK_EXPLORER=https://be.vega.community/rest/
NX_ETHERSCAN_URL=https://etherscan.io
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/
+2 -1
View File
@@ -8,4 +8,5 @@ NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
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,3 +11,4 @@ NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
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,3 +5,4 @@ NX_VEGA_ENV=CUSTOM
NX_BLOCK_EXPLORER=
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
NX_VEGA_EXPLORER_URL=/
@@ -6,6 +6,7 @@ export type AssetBalanceProps = {
assetId: string;
price: string;
showAssetLink?: boolean;
showAssetSymbol?: boolean;
};
/**
@@ -16,18 +17,21 @@ const AssetBalance = ({
assetId,
price,
showAssetLink = true,
showAssetSymbol = false,
}: AssetBalanceProps) => {
const { data: asset } = useAssetDataProvider(assetId);
const { data: asset, loading } = useAssetDataProvider(assetId);
const label =
asset && asset.decimals
!loading && asset && asset.decimals
? addDecimalsFormatNumber(price, asset.decimals)
: price;
return (
<div className="inline-block">
<span>{label}</span>{' '}
{showAssetLink && asset?.id ? <AssetLink assetId={assetId} /> : null}
{showAssetLink && asset?.id ? (
<AssetLink showAssetSymbol={showAssetSymbol} assetId={assetId} />
) : null}
</div>
);
};
@@ -13,11 +13,17 @@ const DEFAULT_DECIMALS = 18;
* the governance asset first, which is set by a network parameter
*/
const GovernanceAssetBalance = ({ price }: GovernanceAssetBalanceProps) => {
const { data } = useExplorerGovernanceAssetQuery();
const { data, loading } = useExplorerGovernanceAssetQuery();
if (data && data.networkParameter?.value) {
if (!loading && data && data.networkParameter?.value) {
const governanceAssetId = data.networkParameter.value;
return <AssetBalance price={price} assetId={governanceAssetId} />;
return (
<AssetBalance
price={price}
showAssetSymbol={true}
assetId={governanceAssetId}
/>
);
} else {
return (
<div className="inline-block">
@@ -20,7 +20,7 @@ export const PageHeader = ({
copy = false,
className,
}: PageHeaderProps) => {
const titleClasses = 'text-4xl xl:text-5xl uppercase font-alpha calt';
const titleClasses = 'text-xl uppercase font-alpha calt';
return (
<header className={className}>
<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
return (
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
<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">
<IconForBundleStatus status={status} />
<h1 className="text-xl pb-1">
{status === 'STATUS_ENABLED'
@@ -1,4 +1,3 @@
export { TxList } from './tx-list';
export { TxOrderType } from './tx-order-type';
export { TxsInfiniteList } from './txs-infinite-list';
export { TxsInfiniteListItem } from './txs-infinite-list-item';
@@ -1,14 +0,0 @@
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 isNumber from 'lodash/isNumber';
const TRUNCATE_LENGTH = 5;
const TRUNCATE_LENGTH = 10;
export const TxsInfiniteListItem = ({
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"
>
<div
className="text-sm col-span-10 xl:col-span-3 leading-none"
className="text-sm col-span-10 md:col-span-3 leading-none"
data-testid="tx-hash"
>
<span className="xl:hidden uppercase text-vega-dark-300">
<span className="md:hidden uppercase text-vega-dark-300">
ID:&nbsp;
</span>
<TruncatedLink
@@ -48,10 +48,10 @@ export const TxsInfiniteListItem = ({
/>
</div>
<div
className="text-sm col-span-10 xl:col-span-3 leading-none"
className="text-sm col-span-10 md:col-span-3 leading-none"
data-testid="pub-key"
>
<span className="xl:hidden uppercase text-vega-dark-300">
<span className="md:hidden uppercase text-vega-dark-300">
By:&nbsp;
</span>
<TruncatedLink
@@ -61,14 +61,14 @@ export const TxsInfiniteListItem = ({
endChars={TRUNCATE_LENGTH}
/>
</div>
<div className="text-sm col-span-5 xl:col-span-2 leading-none flex items-center">
<div className="text-sm col-span-5 md:col-span-2 leading-none flex items-center">
<TxOrderType orderType={type} command={command} />
</div>
<div
className="text-sm col-span-3 xl:col-span-1 leading-none flex items-center"
className="text-sm col-span-3 md:col-span-1 leading-none flex items-center"
data-testid="tx-block"
>
<span className="xl:hidden uppercase text-vega-dark-300">
<span className="md:hidden uppercase text-vega-dark-300">
Block:&nbsp;
</span>
<TruncatedLink
@@ -79,10 +79,10 @@ export const TxsInfiniteListItem = ({
/>
</div>
<div
className="text-sm col-span-2 xl:col-span-1 leading-none flex items-center"
className="text-sm col-span-2 md:col-span-1 leading-none flex items-center"
data-testid="tx-success"
>
<span className="xl:hidden uppercase text-vega-dark-300">
<span className="md:hidden uppercase text-vega-dark-300">
Success:&nbsp;
</span>
{isNumber(code) ? (
@@ -64,9 +64,7 @@ describe('Txs infinite list', () => {
error={Error('test error!')}
/>
);
expect(
screen.getByText('Cannot fetch transaction: Error: test error!')
).toBeInTheDocument();
expect(screen.getByText('Cannot fetch transaction')).toBeInTheDocument();
});
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) => {
let content;
if (error) {
content = t(`Cannot fetch transaction: ${error}`);
content = t(`Cannot fetch transaction`);
} else if (isLoading) {
content = <Loader />;
} else {
@@ -68,7 +68,7 @@ export const TxsInfiniteList = ({
className,
}: TxsInfiniteListProps) => {
const { screenSize } = useScreenDimensions();
const isStacked = ['xs', 'sm', 'md', 'lg'].includes(screenSize);
const isStacked = ['xs', 'sm'].includes(screenSize);
if (!txs) {
if (!areTxsLoading) {
@@ -95,15 +95,15 @@ export const TxsInfiniteList = ({
return (
<div className={className} data-testid="transactions-list">
<div className="xl:grid grid-cols-10 w-full mb-3 hidden text-vega-dark-300 uppercase">
<div className="lg:grid grid-cols-10 w-full mb-3 hidden text-vega-dark-300 uppercase">
<div className="col-span-3">
<span className="hidden xl:inline">Transaction &nbsp;</span>
<span className="hidden xl:inline">{t('Transaction')} &nbsp;</span>
<span>ID</span>
</div>
<div className="col-span-3">Submitted By</div>
<div className="col-span-2">Type</div>
<div className="col-span-1">Block</div>
<div className="col-span-1">Success</div>
<div className="col-span-3">{t('Submitted By')}</div>
<div className="col-span-2">{t('Type')}</div>
<div className="col-span-1">{t('Block')}</div>
<div className="col-span-1">{t('Success')}</div>
</div>
<div data-testid="infinite-scroll-wrapper">
<InfiniteLoader
@@ -1,5 +1,4 @@
import { Routes } from '../../routes/route-names';
import { RenderFetched } from '../render-fetched';
import { TruncatedLink } from '../truncate/truncated-link';
import { TxOrderType } from './tx-order-type';
import { Table, TableRow, TableCell } from '../table';
@@ -9,7 +8,7 @@ import type { BlockExplorerTransactions } from '../../routes/types/block-explore
import isNumber from 'lodash/isNumber';
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
import { getTxsDataUrl } from '../../hooks/use-txs-data';
import { Loader } from '@vegaprotocol/ui-toolkit';
import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit';
import EmptyList from '../empty-list/empty-list';
interface TxsPerBlockProps {
@@ -27,7 +26,7 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
} = useFetch<BlockExplorerTransactions>(url);
return (
<RenderFetched error={error} loading={loading} className="text-body-large">
<AsyncRenderer data={data} error={error} loading={!!loading}>
{data && data.transactions.length > 0 ? (
<div className="overflow-x-auto whitespace-nowrap mb-28">
<Table>
@@ -95,6 +94,6 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
label={t('0 transactions')}
/>
)}
</RenderFetched>
</AsyncRenderer>
);
};
@@ -144,12 +144,12 @@ afterEach(() => {
describe('Block', () => {
it('renders error state if error is present', async () => {
(useFetch as jest.Mock).mockReturnValue({
state: { data: null, loading: false, error: 'asd' },
state: { data: null, loading: false, error: new Error('asd') },
});
render(renderComponent());
expect(screen.getByText(`BLOCK ${blockId}`)).toBeInTheDocument();
expect(screen.getByText('Error retrieving data')).toBeInTheDocument();
expect(screen.getByText('Something went wrong: asd')).toBeInTheDocument();
});
it('renders loading state if present', async () => {
@@ -1,4 +1,3 @@
import React from 'react';
import { Link, useParams } from 'react-router-dom';
import { DATA_SOURCES } from '../../../config';
import { getDateTimeFormat } from '@vegaprotocol/utils';
@@ -12,9 +11,8 @@ import {
TableCell,
} from '../../../components/table';
import { TxsPerBlock } from '../../../components/txs/txs-per-block';
import { Button } from '@vegaprotocol/ui-toolkit';
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { Routes } from '../../route-names';
import { RenderFetched } from '../../../components/render-fetched';
import { t } from '@vegaprotocol/i18n';
import { useFetch } from '@vegaprotocol/react-helpers';
import { NodeLink } from '../../../components/links';
@@ -34,7 +32,7 @@ const Block = () => {
return (
<section>
<RouteTitle data-testid="block-header">{t(`BLOCK ${block}`)}</RouteTitle>
<RenderFetched error={error} loading={loading}>
<AsyncRenderer data={blockData} error={error} loading={!!loading}>
<>
<div className="grid grid-cols-2 gap-2 mb-8">
<Link
@@ -123,7 +121,7 @@ const Block = () => {
</>
)}
</>
</RenderFetched>
</AsyncRenderer>
</section>
);
};
+16 -11
View File
@@ -1,7 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import { useFetch } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../components/route-title';
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { DATA_SOURCES } from '../../config';
import type { TendermintGenesisResponse } from './tendermint-genesis-response';
import { useDocumentTitle } from '../../hooks/use-document-title';
@@ -10,21 +10,26 @@ const Genesis = () => {
useDocumentTitle(['Genesis']);
const {
state: { data: genesis, loading },
state: { data, loading, error },
} = useFetch<TendermintGenesisResponse>(
`${DATA_SOURCES.tendermintUrl}/genesis`
);
if (!genesis?.result.genesis) {
if (loading) {
return <Loader />;
}
return null;
}
return (
<section>
<>
<RouteTitle data-testid="genesis-header">{t('Genesis')}</RouteTitle>
<SyntaxHighlighter data={genesis?.result.genesis} />
</section>
<AsyncRenderer
data={data}
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,6 +49,7 @@ export const MarketPage = () => {
/>
<AsyncRenderer
noDataMessage={t('This chain has no markets')}
errorMessage={t('Could not fetch market') + ' ' + marketId}
data={data}
loading={loading}
error={error}
@@ -21,6 +21,7 @@ import { useDocumentTitle } from '../../hooks/use-document-title';
const PERCENTAGE_PARAMS = [
'governance.proposal.asset.requiredMajority',
'governance.proposal.asset.requiredParticipation',
'governance.proposal.updateAsset.requiredParticipation',
'governance.proposal.freeform.requiredMajority',
'governance.proposal.freeform.requiredParticipation',
'governance.proposal.market.requiredMajority',
@@ -53,6 +54,8 @@ const BIG_NUMBER_PARAMS = [
'governance.proposal.asset.minProposerBalance',
'governance.proposal.market.minProposerBalance',
'governance.proposal.market.minVoterBalance',
'governance.proposal.updateAsset.minProposerBalance',
'governance.proposal.updateAsset.minVoterBalance',
];
export const NetworkParameterRow = ({
@@ -1,4 +1,4 @@
import { Loader, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { RouteTitle } from '../../../components/route-title';
import { t } from '@vegaprotocol/i18n';
import { useExplorerOracleSpecsQuery } from '../__generated__/Oracles';
@@ -8,7 +8,7 @@ import { useScrollToLocation } from '../../../hooks/scroll-to-location';
import filter from 'recursive-key-filter';
const Oracles = () => {
const { data, loading } = useExplorerOracleSpecsQuery();
const { data, loading, error } = useExplorerOracleSpecsQuery();
useDocumentTitle(['Oracles']);
useScrollToLocation();
@@ -16,28 +16,40 @@ const Oracles = () => {
return (
<section>
<RouteTitle data-testid="oracle-specs-heading">{t('Oracles')}</RouteTitle>
{loading ? <Loader /> : null}
{data?.oracleSpecsConnection?.edges
? data.oracleSpecsConnection.edges.map((o) => {
const id = o?.node.dataSourceSpec.spec.id;
if (!id) {
return null;
}
return (
<div id={id} key={id} className="mb-10">
<OracleDetails
id={id}
dataSource={o?.node}
showBroadcasts={false}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(o, ['__typename'])} />
</details>
</div>
);
})
: null}
<AsyncRenderer
data={data}
loading={loading}
error={error}
loadingMessage={t('Loading oracle data...')}
errorMessage={t('Oracle data could not be loaded')}
noDataMessage={t('No oracles found')}
noDataCondition={(data) =>
!data?.oracleSpecsConnection?.edges ||
data.oracleSpecsConnection.edges?.length === 0
}
>
{data?.oracleSpecsConnection?.edges
? data.oracleSpecsConnection.edges.map((o) => {
const id = o?.node.dataSourceSpec.spec.id;
if (!id) {
return null;
}
return (
<div id={id} key={id} className="mb-10">
<OracleDetails
id={id}
dataSource={o?.node}
showBroadcasts={false}
/>
<details>
<summary className="pointer">JSON</summary>
<SyntaxHighlighter data={filter(o, ['__typename'])} />
</details>
</div>
);
})
: null}
</AsyncRenderer>
</section>
);
};
@@ -1,12 +1,11 @@
import { RouteTitle } from '../../../components/route-title';
import { RenderFetched } from '../../../components/render-fetched';
import { truncateByChars } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import { useParams } from 'react-router-dom';
import { useExplorerOracleSpecByIdQuery } from '../__generated__/Oracles';
import { OracleDetails } from '../components/oracle';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { AsyncRenderer, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import filter from 'recursive-key-filter';
import { TruncateInline } from '../../../components/truncate/truncate';
@@ -27,7 +26,14 @@ export const Oracle = () => {
{t(`Oracle `)}
<TruncateInline startChars={5} endChars={5} text={id || '1'} />
</RouteTitle>
<RenderFetched error={error} loading={loading}>
<AsyncRenderer
data={data}
error={error}
loading={loading}
noDataCondition={(data) => !data?.oracleSpec}
errorMessage={t('Could not load oracle data')}
loadingMessage={t('Loading oracle data...')}
>
{data?.oracleSpec ? (
<div id={id} key={id} className="mb-10">
<OracleDetails
@@ -44,7 +50,7 @@ export const Oracle = () => {
) : (
<span></span>
)}
</RenderFetched>
</AsyncRenderer>
</section>
);
};
@@ -48,6 +48,13 @@ query ExplorerPartyAssets($partyId: ID!) {
}
stakingSummary {
currentStakeAvailable
linkings(pagination: { first: 100 }) {
edges {
node {
amount
}
}
}
}
accountsConnection {
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 }, 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, 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 const ExplorerPartyAssetsAccountsFragmentDoc = gql`
fragment ExplorerPartyAssetsAccounts on AccountBalance {
@@ -64,6 +64,13 @@ export const ExplorerPartyAssetsDocument = gql`
}
stakingSummary {
currentStakeAvailable
linkings(pagination: {first: 100}) {
edges {
node {
amount
}
}
}
}
accountsConnection {
edges {
@@ -0,0 +1,24 @@
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,32 +1,9 @@
import { t } from '@vegaprotocol/i18n';
import get from 'lodash/get';
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'),
};
import { AccountManager } from '@vegaprotocol/accounts';
import { useCallback } from 'react';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
interface PartyAccountsProps {
accounts: ExplorerPartyAssetsAccountsFragment[];
partyId: string;
}
/**
@@ -34,49 +11,22 @@ interface PartyAccountsProps {
* probably do with sorting by asset, and then within asset, by type with general
* appearing first and... tbd
*/
export const PartyAccounts = ({ accounts }: PartyAccountsProps) => {
return (
<Table className="max-w-5xl min-w-fit">
<thead>
<TableRow modifier="bordered" className="font-mono">
<td>{t('Type')}</td>
<td>{t('Market')}</td>
<td className="text-right pr-2">{t('Balance')}</td>
<td>{t('Asset')}</td>
</TableRow>
</thead>
<tbody>
{accounts.map((account) => {
const m = get(account, 'market.tradableInstrument.instrument.name');
export const PartyAccounts = ({ partyId }: PartyAccountsProps) => {
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const onClickAsset = useCallback(
(assetId?: string) => {
assetId && openAssetDetailsDialog(assetId);
},
[openAssetDetailsDialog]
);
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>
return (
<div className="block min-h-44 h-60 4 w-full border-red-800 relative">
<AccountManager
partyId={partyId}
onClickAsset={onClickAsset}
isReadOnly={true}
/>
</div>
);
};
@@ -0,0 +1,63 @@
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>
);
};
@@ -0,0 +1,83 @@
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>
);
};
@@ -0,0 +1,24 @@
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,24 +1,26 @@
import { getNodes } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import { SubHeading } from '../../../components/sub-heading';
import { Panel } from '../../../components/panel';
import { toNonHex } from '../../../components/search/detect-search';
import { useTxsData } from '../../../hooks/use-txs-data';
import { TxsInfiniteList } from '../../../components/txs';
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 GovernanceAssetBalance from '../../../components/asset-balance/governance-asset-balance';
import { PartyAccounts } from './components/party-accounts';
import { Icon, Intent, Notification, Splash } from '@vegaprotocol/ui-toolkit';
import { aggregatedAccountsDataProvider } from '@vegaprotocol/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 } = useParams<{ party: string }>();
useDocumentTitle(['Public keys', party || '-']);
const navigate = useNavigate();
const partyId = toNonHex(party ? party : '');
const { isMobile } = useScreenDimensions();
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
@@ -28,71 +30,72 @@ const Party = () => {
filters,
});
const partyRes = useExplorerPartyAssetsQuery({
// Don't cache data for this query, party information can move quite quickly
fetchPolicy: 'network-only',
variables: { partyId: partyId },
skip: !party,
const variables = useMemo(() => ({ partyId }), [partyId]);
const {
data: AccountData,
loading: AccountLoading,
error: AccountError,
} = useDataProvider({
dataProvider: aggregatedAccountsDataProvider,
variables,
});
const p = partyRes.data?.partiesConnection?.edges[0].node;
const header = p?.id ? (
<PageHeader
title={p.id}
copy
truncateStart={visibleChars}
truncateEnd={visibleChars}
/>
) : (
<Panel>
<p>No data found for public key {party}</p>
</Panel>
);
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
);
if (!isValidPartyId(partyId)) {
return (
<div className="max-w-sm mx-auto">
<Notification
message={t('Invalid party ID')}
intent={Intent.Danger}
buttonProps={{
text: t('Go back'),
action: () => navigate(-1),
className: 'py-1',
size: 'sm',
}}
/>
</div>
);
}
return (
<section>
<h1
className="font-alpha calt uppercase font-xl mb-4 text-vega-dark-100 dark:text-vega-light-100"
data-testid="parties-header"
>
{t('Public key')}
</h1>
{partyRes.data ? (
<>
{header}
<SubHeading>{t('Asset data')}</SubHeading>
{accounts ? <PartyAccounts accounts={accounts} /> : null}
{staking}
<PageHeader
title={partyId}
copy
truncateStart={visibleChars}
truncateEnd={visibleChars}
/>
<SubHeading>{t('Transactions')}</SubHeading>
<TxsInfiniteList
hasMoreTxs={hasMoreTxs}
areTxsLoading={loading}
txs={txsData}
loadMoreTxs={loadTxs}
error={error}
className="mb-28"
/>
</>
) : null}
<div className="grid md:grid-flow-col grid-flow-row md:space-x-4 grid-cols-1 md:grid-cols-2 w-full">
<PartyBlockAccounts
accountError={AccountError}
accountLoading={AccountLoading}
accountData={AccountData}
partyId={partyId}
/>
<PartyBlockStake
accountError={AccountError}
accountLoading={AccountLoading}
partyId={partyId}
/>
</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>
);
};
@@ -0,0 +1,7 @@
import { Outlet } from 'react-router-dom';
const PartiesSubPage = () => {
return <Outlet />;
};
export default PartiesSubPage;
@@ -1,35 +0,0 @@
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 };
+38 -20
View File
@@ -14,7 +14,6 @@ import { Block } from './blocks/id';
import { Blocks } from './blocks/home';
import { Tx } from './txs/id';
import { TxsList } from './txs/home';
import { PendingTxs } from './pending';
import flags from '../config/flags';
import { t } from '@vegaprotocol/i18n';
import { Routes } from './route-names';
@@ -29,6 +28,7 @@ import compact from 'lodash/compact';
import { AssetLink, MarketLink } from '../components/links';
import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
import { remove0x } from '@vegaprotocol/utils';
import { PartyAccountsByAsset } from './parties/id/accounts';
export type Navigable = {
path: string;
@@ -75,14 +75,43 @@ const partiesRoutes: Route[] = flags.parties
},
{
path: ':party',
element: <PartySingle />,
handle: {
breadcrumb: (params: Params<string>) => (
<Link to={linkTo(Routes.PARTIES, params.party)}>
{truncateMiddle(params.party as string)}
</Link>
),
},
element: <Party />,
children: [
{
index: true,
element: <PartySingle />,
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');
},
},
},
],
},
],
},
],
},
@@ -237,17 +266,6 @@ export const routerConfig: Route[] = [
breadcrumb: () => <Link to={Routes.TX}>{t('Transactions')}</Link>,
},
children: [
{
path: 'pending',
element: <PendingTxs />,
handle: {
breadcrumb: () => (
<Link to={linkTo(Routes.TX, 'pending')}>
{t('Pending transactions')}
</Link>
),
},
},
{
path: ':txHash',
element: <Tx />,
@@ -10,7 +10,7 @@ interface TxDetailsProps {
export const txDetailsTruncateLength = 30;
export const TxDetails = ({ txData, pubKey, className }: TxDetailsProps) => {
export const TxDetails = ({ txData, pubKey }: TxDetailsProps) => {
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
@@ -28,6 +28,7 @@ import {
} from '@vegaprotocol/environment';
import classNames from 'classnames';
import { NodeStatus, NodeStatusMapping } from '@vegaprotocol/types';
import { PartyLink } from '../../components/links';
type RateProps = {
value: BigNumber | number | undefined;
@@ -191,7 +192,9 @@ export const ValidatorsPage = () => {
<KeyValueTable>
<KeyValueTableRow>
<div>{t('ID')}</div>
<div className="break-all text-xs">{v.id}</div>
<div className="break-all text-xs font-mono">
{v.id}
</div>
</KeyValueTableRow>
<KeyValueTableRow>
<div>{t('Status')}</div>
@@ -218,12 +221,14 @@ export const ValidatorsPage = () => {
</div>
</KeyValueTableRow>
<KeyValueTableRow>
<div>{t('Public key')}</div>
<div className="break-all text-xs">{v.pubkey}</div>
<div>{t('Key')}</div>
<div className="break-all text-xs">
<PartyLink id={v.pubkey} />
</div>
</KeyValueTableRow>
<KeyValueTableRow>
<div>{t('Ethereum address')}</div>
<div className="break-all text-xs">
<div className="break-all text-xs font-mono">
<EtherscanLink address={v.ethereumAddress} />{' '}
<CopyWithTooltip text={v.ethereumAddress}>
<button title={t('Copy address to clipboard')}>
@@ -234,7 +239,9 @@ export const ValidatorsPage = () => {
</KeyValueTableRow>
<KeyValueTableRow>
<div>{t('Tendermint public key')}</div>
<div className="break-all text-xs">{v.tmPubkey}</div>
<div className="break-all text-xs font-mono">
{v.tmPubkey}
</div>
</KeyValueTableRow>
<KeyValueTableRow>
+5
View File
@@ -32,6 +32,11 @@
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 */
.ag-theme-balham {
--ag-background-color: theme(colors.white);
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_URL=https://api.vega.community/graphql
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io
@@ -4,12 +4,11 @@ import {
navigation,
} from '../../support/common.functions';
import {
convertUnixTimestampToDateformat,
createRawProposal,
createTenDigitUnixTimeStampForSpecifiedDays,
enterUniqueFreeFormProposalBody,
generateFreeFormProposalTitle,
getGovernanceProposalDateFormatForSpecifiedDays,
getDateFormatForSpecifiedDays,
getProposalIdFromList,
getProposalInformationFromTable,
getSubmittedProposalFromProposalList,
@@ -23,6 +22,7 @@ import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../../../governan
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../../../governance-e2e/src/support/wallet-teardown.functions';
import type { testFreeformProposal } from '../../support/common-interfaces';
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
const proposalVoteProgressForPercentage =
'[data-testid="vote-progress-indicator-percentage-for"]';
@@ -99,20 +99,22 @@ describe(
getSubmittedProposalFromProposalList(proposalTitle).within(() =>
cy.get(viewProposalButton).click()
);
convertUnixTimestampToDateformat(proposalTimeStamp).then(
(closingDate) => {
getProposalInformationFromTable('Closes on')
.contains(closingDate)
.should('be.visible');
}
);
getGovernanceProposalDateFormatForSpecifiedDays(0).then(
(proposalDate) => {
getProposalInformationFromTable('Proposed on')
.contains(proposalDate)
.should('be.visible');
}
);
cy.wrap(
formatDateWithLocalTimezone(new Date(proposalTimeStamp * 1000))
).then((closingDate) => {
getProposalInformationFromTable('Closes on')
.contains(closingDate)
.should('be.visible');
});
cy.wrap(
formatDateWithLocalTimezone(
new Date(createTenDigitUnixTimeStampForSpecifiedDays(0) * 1000)
)
).then((proposalDate) => {
getProposalInformationFromTable('Proposed on')
.contains(proposalDate)
.should('be.visible');
});
});
it('Newly created proposal details - shows default status set to fail', function () {
@@ -155,18 +157,16 @@ describe(
cy.getByTestId('vote-buttons').contains('against').should('be.visible');
cy.getByTestId('vote-buttons').contains('for').should('be.visible');
voteForProposal('for');
getGovernanceProposalDateFormatForSpecifiedDays(0, 'shortMonth').then(
(votedDate) => {
// 3001-VOTE-051
// 3001-VOTE-093
cy.contains('You voted:')
.siblings()
.contains('For')
.siblings()
.contains(votedDate)
.should('be.visible');
}
);
getDateFormatForSpecifiedDays(0).then((votedDate) => {
// 3001-VOTE-051
// 3001-VOTE-093
cy.contains('You voted:')
.siblings()
.contains('For')
.siblings()
.contains(votedDate)
.should('be.visible');
});
cy.get(proposalVoteProgressForPercentage) // 3001-VOTE-072
.contains('100.00%')
.and('be.visible');
@@ -29,6 +29,8 @@ const tableReceiverAddress = '[col-id="details.receiverAddress"]';
const tableWithdrawnTimeStamp = '[col-id="withdrawnTimestamp"]';
const tableWithdrawnStatus = '[col-id="status"]';
const tableCreatedTimeStamp = '[col-id="createdTimestamp"]';
const toastContent = 'toast-content';
const toastPanel = 'toast-panel';
const usdtName = 'USDC (local)';
const usdcEthAddress = '0x1b8a1B6CBE5c93609b46D1829Cc7f3Cb8eeE23a0';
const usdcSymbol = 'tUSDC';
@@ -149,7 +151,6 @@ context(
});
});
// Skipping because of bug #1857
it('Able to withdraw asset: -eth wallet not connected', function () {
const ethWalletAddress = Cypress.env('ethWalletPublicKey');
cy.reload();
@@ -187,8 +188,26 @@ context(
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/address/');
cy.get(tableCreatedTimeStamp).should('not.be.empty');
cy.getByTestId(completeWithdrawalButton).click();
// Unable to complete withdrawal in Capsule
});
ethereumWalletConnect();
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');
});
});
@@ -218,7 +237,10 @@ context(
});
function waitForAssetsDisplayed(expectedAsset: string) {
cy.getByTestId('currency-title').should('contain.text', expectedAsset);
cy.getByTestId('currency-title', txTimeout).should(
'contain.text',
expectedAsset
);
}
}
);
@@ -27,7 +27,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
cy.getByTestId('app-announcement').should('not.exist');
});
it('should show open or enacted proposals with proposal summary', function () {
it('should show open or enacted proposals without proposal summary', function () {
cy.get('body').then(($body) => {
if (!$body.find('[data-testid="proposals-list-item"]').length) {
cy.createMarket();
@@ -43,12 +43,6 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
.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')
.invoke('text')
.should('not.be.empty');
@@ -101,15 +101,15 @@ context(
);
cy.getByTestId('protocol-upgrade-proposal-release-tag').should(
'have.text',
'Vega release tagv1'
'Vega release tag: v1'
);
cy.getByTestId('protocol-upgrade-proposal-block-height').should(
'have.text',
'Upgrade block height2015942'
'Upgrade block height: 2015942'
);
cy.getByTestId('protocol-upgrade-proposal-status').should(
'have.text',
'Approved'
'Approved '
);
});
});
@@ -1,3 +1,4 @@
import { format } from 'date-fns';
import { closeDialog, navigateTo, navigation } from './common.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from './staking.functions';
@@ -15,35 +16,6 @@ const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const epochTimeout = Cypress.env('epochTimeout');
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(
durationDays: number
) {
@@ -52,6 +24,13 @@ export function createTenDigitUnixTimeStampForSpecifiedDays(
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) {
cy.fixture('/proposals/raw.json').then((rawProposal) => {
rawProposal.terms.closingTimestamp = timestamp;
@@ -104,16 +83,6 @@ export function getProposalIdFromList(proposalTitle: string) {
});
}
export function getGovernanceProposalDateFormatForSpecifiedDays(
days: number,
shortOrLong?: string
) {
return convertUnixTimestampToDateformat(
createTenDigitUnixTimeStampForSpecifiedDays(days),
shortOrLong
);
}
export function getProposalInformationFromTable(heading: string) {
return cy.get(proposalInformationTableRows).contains(heading).siblings();
}
+1 -1
View File
@@ -1,7 +1,7 @@
# App configuration variables
NX_VEGA_ENV=MAINNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_URL=https://api.vega.community/graphql
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_ETHERSCAN_URL=https://etherscan.io
+5
View File
@@ -0,0 +1,5 @@
function ReactMarkdown({ children }) {
return <div>{children}</div>;
}
export default ReactMarkdown;
@@ -23,6 +23,7 @@ export const ConnectToVega = () => {
openVegaWalletDialog();
}}
data-testid="connect-to-vega-wallet-btn"
variant="primary"
>
{t('connectVegaWallet')}
</Button>
@@ -23,7 +23,7 @@ export const Heading = ({
})}
>
<h1
className={classNames('font-alpha calt text-5xl', {
className={classNames('font-alpha calt text-5xl break-words', {
'mt-0': !marginTop,
'mb-0': !marginBottom,
})}
@@ -21,10 +21,10 @@ export const useGetUserBalances = (account: string | undefined) => {
token.allowance(account, config.staking_bridge_contract.address),
]);
const balance = toBigNum(b, decimals);
const walletBalance = toBigNum(w, decimals);
const lien = toBigNum(stats.lien, decimals);
const allowance = toBigNum(a, decimals);
const balance = toBigNum(b.toString(), decimals);
const walletBalance = toBigNum(w.toString(), decimals);
const lien = toBigNum(stats.lien.toString(), decimals);
const allowance = toBigNum(a.toString(), decimals);
return {
balanceFormatted: balance,
@@ -21,8 +21,14 @@ export function useRefreshAssociatedBalances() {
]);
updateBalances({
walletAssociatedBalance: toBigNum(walletAssociatedBalance, decimals),
vestingAssociatedBalance: toBigNum(vestingAssociatedBalance, decimals),
walletAssociatedBalance: toBigNum(
walletAssociatedBalance.toString(),
decimals
),
vestingAssociatedBalance: toBigNum(
vestingAssociatedBalance.toString(),
decimals
),
});
},
[staking, vesting, updateBalances, decimals]
@@ -31,12 +31,18 @@ export const useRefreshBalances = (address: string) => {
pubKey ? vesting.stake_balance(address, pubKey) : null,
]);
const balance = toBigNum(b, decimals);
const walletBalance = toBigNum(w, decimals);
const lien = toBigNum(stats.lien, decimals);
const allowance = toBigNum(a, decimals);
const walletAssociatedBalance = toBigNum(walletStakeBalance, decimals);
const vestingAssociatedBalance = toBigNum(vestingStakeBalance, decimals);
const balance = toBigNum(b.toString(), decimals);
const walletBalance = toBigNum(w.toString(), decimals);
const lien = toBigNum(stats.lien.toString(), decimals);
const allowance = toBigNum(a.toString(), decimals);
const walletAssociatedBalance = toBigNum(
walletStakeBalance ? walletStakeBalance.toString() : 0,
decimals
);
const vestingAssociatedBalance = toBigNum(
vestingStakeBalance ? vestingStakeBalance.toString() : 0,
decimals
);
updateBalances({
balanceFormatted: balance,
+11 -3
View File
@@ -201,7 +201,7 @@
"NewFreeform": "Freeform",
"tokenVotes": "Token votes",
"liquidityVotes": "Liquidity votes",
"yourVote": "Your vote",
"castYourVote": "Cast your vote",
"for": "For",
"against": "Against",
"majorityRequired": "Majority Required",
@@ -587,7 +587,7 @@
"tokensAgainstProposal": "Tokens against proposal",
"participationRequired": "Participation required",
"numberOfVotingParties": "Number of voting parties",
"totalTokensVotes": "Total yes tokens",
"totalTokensVotes": "Total tokens voted",
"totalTokenVotedPercentage": "Total tokens voted percentage",
"numberOfForVotes": "Number of votes for",
"numberOfAgainstVotes": "Number of votes against",
@@ -631,7 +631,10 @@
"New market": "New market",
"Market change": "Market change",
"Network parameter": "Network parameter",
"Change": "Change",
"Unknown proposal": "Unknown proposal",
"ERC20ContractAddress": "ERC20 contract address",
"MaxFaucetAmountMint": "Max faucet amount mint",
"Code": "Code",
"settled future": "settled future",
"Symbol": "Symbol",
@@ -677,6 +680,7 @@
"NewProposal": "New proposal",
"ProposalTypeQuestion": "What type of proposal would you like to make?",
"NetworkParameterProposal": "Update network parameter proposal",
"parameter": "parameter",
"NewMarketProposal": "New market proposal",
"UpdateMarketProposal": "Update market proposal",
"NewAssetProposal": "New asset proposal",
@@ -694,6 +698,7 @@
"UpdateMarket": "Update market",
"NewAsset": "New asset",
"UpdateAsset": "Update asset",
"AssetID": "Asset ID",
"Freeform": "Freeform",
"RawProposal": "Let me choose (raw proposal)",
"UseMin": "Use minimum",
@@ -737,6 +742,7 @@
"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.",
"FreeformProposal": "Freeform proposal",
"Id": "ID",
"unknownReason": "unknown reason",
"votingEnded": "Voting has ended.",
"STATUS": "STATUS",
@@ -791,5 +797,7 @@
"67% voting power required": "67% voting power required",
"Token": "Token",
"associateVegaNow": "Associate $VEGA now",
"disconnectedNotice": "You have been disconnected. Connect your ETH wallet to the {{correctNetwork}} network to use this app."
"disconnectedNotice": "You have been disconnected. Connect your ETH wallet to the {{correctNetwork}} network to use this app.",
"connectAVegaWalletToVote": "Connect a Vega wallet with $VEGA tokens to vote on a proposal.",
"findOutMoreAboutHowToVote": "Find out more about how to vote on Vega"
}
@@ -1,7 +1,11 @@
import { useTranslation } from 'react-i18next';
import * as Schema from '@vegaprotocol/types';
import { Icon } from '@vegaprotocol/ui-toolkit';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
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 = ({
proposal,
@@ -9,19 +13,63 @@ export const CurrentProposalState = ({
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { t } = useTranslation();
let className = 'text-white';
let proposalStatus: ReactNode;
let variant = 'tertiary' as ProposalInfoLabelVariant;
if (
proposal?.state === Schema.ProposalState.STATE_DECLINED ||
proposal?.state === Schema.ProposalState.STATE_FAILED ||
proposal?.state === Schema.ProposalState.STATE_REJECTED
) {
className = 'text-danger';
} else if (
proposal?.state === Schema.ProposalState.STATE_ENACTED ||
proposal?.state === Schema.ProposalState.STATE_PASSED
) {
className = 'text-white';
switch (proposal?.state) {
case ProposalState.STATE_ENACTED: {
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Enacted')}</span>
<Icon name={'tick'} />
</>
);
break;
}
case ProposalState.STATE_PASSED: {
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Passed')}</span>
<Icon name={'tick'} />
</>
);
break;
}
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
proposalStatus = (
<>
<span className="mr-2">{t('voteState_WaitingForNodeVote')}</span>
<Icon name={'time'} />
</>
);
break;
}
case ProposalState.STATE_OPEN: {
variant = 'primary' as ProposalInfoLabelVariant;
proposalStatus = <>{t('voteState_Open')}</>;
break;
}
case ProposalState.STATE_DECLINED: {
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Declined')}</span>
<Icon name={'cross'} />
</>
);
break;
}
case ProposalState.STATE_REJECTED: {
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Rejected')}</span>
<Icon name={'warning-sign'} />
</>
);
break;
}
}
return <span className={className}>{t(`${proposal?.state}`)}</span>;
return (
<ProposalInfoLabel variant={variant}>{proposalStatus}</ProposalInfoLabel>
);
};
@@ -16,17 +16,12 @@ it('Renders all data for table', () => {
render(<ProposalChangeTable proposal={proposal} />);
expect(screen.getByText('ID')).toBeInTheDocument();
expect(screen.getByText(proposal?.id as string)).toBeInTheDocument();
expect(screen.getByText('State')).toBeInTheDocument();
expect(screen.getByText('Open')).toBeInTheDocument();
expect(screen.getByText('Closes on')).toBeInTheDocument();
expect(
screen.getByText(
formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime))
)
).toBeInTheDocument();
expect(screen.getByText('Proposed enactment')).toBeInTheDocument();
expect(
screen.getByText(
@@ -35,17 +30,12 @@ it('Renders all data for table', () => {
)
)
).toBeInTheDocument();
expect(screen.getByText('Proposed by')).toBeInTheDocument();
expect(screen.getByText(proposal?.party.id ?? '')).toBeInTheDocument();
expect(screen.getByText('Proposed on')).toBeInTheDocument();
expect(
screen.getByText(formatDateWithLocalTimezone(new Date(proposal?.datetime)))
).toBeInTheDocument();
expect(screen.getByText('Type')).toBeInTheDocument();
expect(screen.getByText('Network parameter')).toBeInTheDocument();
});
it('Changes data based on if data is in future or past', () => {
@@ -53,17 +43,12 @@ it('Changes data based on if data is in future or past', () => {
state: ProposalState.STATE_ENACTED,
});
render(<ProposalChangeTable proposal={proposal} />);
expect(screen.getByText('State')).toBeInTheDocument();
expect(screen.getByText('Enacted')).toBeInTheDocument();
expect(screen.getByText('Closed on')).toBeInTheDocument();
expect(
screen.getByText(
formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime))
)
).toBeInTheDocument();
expect(screen.getByText('Enacted on')).toBeInTheDocument();
expect(
screen.getByText(
@@ -104,7 +89,6 @@ it('Renders error details and rejection reason if present', () => {
render(<ProposalChangeTable proposal={proposal} />);
expect(screen.getByText('Error details')).toBeInTheDocument();
expect(screen.getByText(errorDetails)).toBeInTheDocument();
expect(screen.getByText('Rejection reason')).toBeInTheDocument();
expect(
screen.getByText(ProposalRejectionReason.PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE)
@@ -6,7 +6,6 @@ import {
KeyValueTableRow,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { CurrentProposalState } from '../current-proposal-state';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
@@ -20,16 +19,12 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
const terms = proposal?.terms;
return (
<RoundedWrapper>
<RoundedWrapper paddingBottom={true}>
<KeyValueTable data-testid="proposal-change-table">
<KeyValueTableRow>
{t('id')}
{proposal?.id}
</KeyValueTableRow>
<KeyValueTableRow>
{t('state')}
<CurrentProposalState proposal={proposal} />
</KeyValueTableRow>
<KeyValueTableRow>
{isFuture(new Date(terms?.closingDatetime))
? t('closesOn')
@@ -50,26 +45,24 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
{t('proposedBy')}
<span style={{ wordBreak: 'break-word' }}>{proposal?.party.id}</span>
</KeyValueTableRow>
<KeyValueTableRow>
<KeyValueTableRow
noBorder={!proposal?.rejectionReason && !proposal?.errorDetails}
>
{t('proposedOn')}
{formatDateWithLocalTimezone(new Date(proposal?.datetime))}
</KeyValueTableRow>
{proposal?.rejectionReason ? (
<KeyValueTableRow>
<KeyValueTableRow noBorder={!proposal?.errorDetails}>
{t('rejectionReason')}
{proposal.rejectionReason}
</KeyValueTableRow>
) : null}
{proposal?.errorDetails ? (
<KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
{t('errorDetails')}
{proposal.errorDetails}
</KeyValueTableRow>
) : null}
<KeyValueTableRow>
{t('type')}
{t(`${proposal?.terms.change.__typename}`)}
</KeyValueTableRow>
</KeyValueTable>
</RoundedWrapper>
);
@@ -1,68 +1,68 @@
import { render, screen } from '@testing-library/react';
import { generateProposal } from '../../test-helpers/generate-proposals';
import {
generateNoVotes,
generateProposal,
generateYesVotes,
} from '../../test-helpers/generate-proposals';
import { ProposalHeader } from './proposal-header';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
import { lastWeek, nextWeek } from '../../test-helpers/mocks';
const renderComponent = (proposal: ProposalQuery['proposal']) => (
<ProposalHeader proposal={proposal} />
);
const renderComponent = (
proposal: ProposalQuery['proposal'],
isListItem = true
) => render(<ProposalHeader proposal={proposal} isListItem={isListItem} />);
describe('Proposal header', () => {
it('Renders New market proposal', () => {
render(
renderComponent(
generateProposal({
rationale: {
title: 'New some market',
description: 'A new some market',
},
terms: {
change: {
__typename: 'NewMarket',
instrument: {
__typename: 'InstrumentConfiguration',
name: 'Some market',
code: 'FX:BTCUSD/DEC99',
futureProduct: {
__typename: 'FutureProduct',
settlementAsset: {
__typename: 'Asset',
symbol: 'tGBP',
},
renderComponent(
generateProposal({
rationale: {
title: 'New some market',
description: 'A new some market',
},
terms: {
change: {
__typename: 'NewMarket',
instrument: {
__typename: 'InstrumentConfiguration',
name: 'Some market',
code: 'FX:BTCUSD/DEC99',
futureProduct: {
__typename: 'FutureProduct',
settlementAsset: {
__typename: 'Asset',
symbol: 'tGBP',
},
},
},
},
})
)
},
})
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'New some market'
);
expect(screen.getByTestId('proposal-type')).toHaveTextContent('New market');
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
'A new some market'
);
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'tGBP settled future.'
);
});
it('Renders Update market proposal', () => {
render(
renderComponent(
generateProposal({
rationale: {
title: 'New market id',
renderComponent(
generateProposal({
rationale: {
title: 'New market id',
},
terms: {
change: {
__typename: 'UpdateMarket',
marketId: 'MarketId',
},
terms: {
change: {
__typename: 'UpdateMarket',
marketId: 'MarketId',
},
},
})
)
},
})
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'New market id'
@@ -79,53 +79,49 @@ describe('Proposal header', () => {
});
it('Renders New asset proposal - ERC20', () => {
render(
renderComponent(
generateProposal({
rationale: {
title: 'New asset: Fake currency',
description: '',
},
terms: {
change: {
__typename: 'NewAsset',
name: 'Fake currency',
symbol: 'FAKE',
source: {
__typename: 'ERC20',
contractAddress: '0x0',
},
renderComponent(
generateProposal({
rationale: {
title: 'New asset: Fake currency',
description: '',
},
terms: {
change: {
__typename: 'NewAsset',
name: 'Fake currency',
symbol: 'FAKE',
source: {
__typename: 'ERC20',
contractAddress: '0x0',
},
},
})
)
},
})
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'New asset: Fake currency'
);
expect(screen.getByTestId('proposal-type')).toHaveTextContent('New asset');
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Symbol: FAKE. ERC20 0x0'
'Symbol: FAKE. ERC20 contract address: 0x0'
);
});
it('Renders New asset proposal - BuiltInAsset', () => {
render(
renderComponent(
generateProposal({
terms: {
change: {
__typename: 'NewAsset',
name: 'Fake currency',
symbol: 'BIA',
source: {
__typename: 'BuiltinAsset',
maxFaucetAmountMint: '300',
},
renderComponent(
generateProposal({
terms: {
change: {
__typename: 'NewAsset',
name: 'Fake currency',
symbol: 'BIA',
source: {
__typename: 'BuiltinAsset',
maxFaucetAmountMint: '300',
},
},
})
)
},
})
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Unknown proposal'
@@ -137,24 +133,22 @@ describe('Proposal header', () => {
});
it('Renders Update network', () => {
render(
renderComponent(
generateProposal({
rationale: {
title: 'Network parameter',
},
terms: {
change: {
__typename: 'UpdateNetworkParameter',
networkParameter: {
__typename: 'NetworkParameter',
key: 'Network key',
value: 'Network value',
},
renderComponent(
generateProposal({
rationale: {
title: 'Network parameter',
},
terms: {
change: {
__typename: 'UpdateNetworkParameter',
networkParameter: {
__typename: 'NetworkParameter',
key: 'Network key',
value: 'Network value',
},
},
})
)
},
})
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Network parameter'
@@ -167,122 +161,211 @@ describe('Proposal header', () => {
);
});
it('Renders Freeform network - short rationale', () => {
render(
renderComponent(
generateProposal({
id: 'short',
rationale: {
title: '0x0',
it('Renders Freeform proposal - short rationale', () => {
renderComponent(
generateProposal({
id: 'short',
rationale: {
title: '0x0',
},
terms: {
change: {
__typename: 'NewFreeform',
},
terms: {
change: {
__typename: 'NewFreeform',
},
},
})
)
},
})
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent('0x0');
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
expect(
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
expect(screen.getByTestId('proposal-details')).toHaveTextContent('short');
});
it('Renders Freeform proposal - long rationale (105 chars)', () => {
render(
renderComponent(
generateProposal({
id: 'long',
rationale: {
title: '0x0',
description:
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
it('Renders Freeform proposal - long rationale (105 chars) - listing', () => {
renderComponent(
generateProposal({
id: 'long',
rationale: {
title: '0x0',
description:
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
},
terms: {
change: {
__typename: 'NewFreeform',
},
terms: {
change: {
__typename: 'NewFreeform',
},
},
})
)
},
})
);
// 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-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(
'Class aptent taciti sociosqu ad litora torquent per conubia'
/Class aptent/
);
expect(screen.getByTestId('proposal-details')).toHaveTextContent('long');
});
// Remove once proposals have rationale and re-enable above tests
it('Renders Freeform proposal - id for title', () => {
render(
renderComponent(
generateProposal({
id: 'freeform id',
rationale: {
title: 'freeform',
renderComponent(
generateProposal({
id: 'freeform id',
rationale: {
title: 'freeform',
},
terms: {
change: {
__typename: 'NewFreeform',
},
terms: {
change: {
__typename: 'NewFreeform',
},
},
})
)
},
})
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent('freeform');
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
expect(
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
expect(screen.queryByTestId('proposal-details')).toHaveTextContent(
'freeform id'
);
});
it('Renders asset change proposal header', () => {
render(
renderComponent(
generateProposal({
terms: {
change: {
__typename: 'UpdateAsset',
assetId: 'foo',
},
renderComponent(
generateProposal({
terms: {
change: {
__typename: 'UpdateAsset',
assetId: 'foo',
},
})
)
},
})
);
expect(screen.getByTestId('proposal-type')).toHaveTextContent(
'Update asset'
);
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Update asset'
);
expect(screen.getByText('foo')).toBeInTheDocument();
});
it("Renders unknown proposal if it's a different proposal type", () => {
render(
renderComponent(
generateProposal({
terms: {
change: {
// @ts-ignore unknown proposal
__typename: 'Foo',
},
renderComponent(
generateProposal({
terms: {
change: {
// @ts-ignore unknown proposal
__typename: 'Foo',
},
})
)
},
})
);
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
'Unknown proposal'
);
});
it('Renders proposal state: Enacted', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_ENACTED,
terms: {
enactmentDatetime: lastWeek.toString(),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Enacted');
});
it('Renders proposal state: Passed', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_PASSED,
terms: {
closingDatetime: lastWeek.toString(),
enactmentDatetime: nextWeek.toString(),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Passed');
});
it('Renders proposal state: Waiting for node vote', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
terms: {
enactmentDatetime: nextWeek.toString(),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent(
'Waiting for node vote'
);
});
it('Renders proposal state: Open', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_OPEN,
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(3000, 1000000000000000000),
no: generateNoVotes(0),
},
terms: {
closingDatetime: nextWeek.toString(),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
});
it('Renders proposal state: Declined - majority not reached', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_DECLINED,
terms: {
enactmentDatetime: lastWeek.toString(),
},
votes: {
no: generateNoVotes(1, 1000000000000000000),
yes: generateYesVotes(1, 1000000000000000000),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined');
});
it('Renders proposal state: Rejected', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_REJECTED,
terms: {
enactmentDatetime: lastWeek.toString(),
},
rejectionReason:
ProposalRejectionReason.PROPOSAL_ERROR_INVALID_FUTURE_PRODUCT,
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Rejected');
});
});
@@ -1,23 +1,27 @@
import { useTranslation } from 'react-i18next';
import { Intent, Lozenge } from '@vegaprotocol/ui-toolkit';
import { Lozenge } from '@vegaprotocol/ui-toolkit';
import { shorten } from '@vegaprotocol/utils';
import { Heading, SubHeading } from '../../../../components/heading';
import type { ReactNode } from 'react';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import ReactMarkdown from 'react-markdown';
import { truncateMiddle } from '../../../../lib/truncate-middle';
import { CurrentProposalState } from '../current-proposal-state';
import { ProposalInfoLabel } from '../proposal-info-label';
export const ProposalHeader = ({
proposal,
useSubHeading = true,
isListItem = true,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
useSubHeading?: boolean;
isListItem?: boolean;
}) => {
const { t } = useTranslation();
const change = proposal?.terms.change;
let details: ReactNode;
let proposalType: ReactNode;
let proposalType = '';
let title = proposal?.rationale.title.trim();
let description = proposal?.rationale.description.trim();
@@ -30,10 +34,12 @@ export const ProposalHeader = ({
switch (change?.__typename) {
case 'NewMarket': {
proposalType = t('NewMarket');
proposalType = 'NewMarket';
details = (
<>
{t('Code')}: {change.instrument.code}.{' '}
<span>
{t('Code')}: {change.instrument.code}.
</span>{' '}
{change.instrument.futureProduct?.settlementAsset.symbol ? (
<>
<span className="font-semibold">
@@ -49,54 +55,61 @@ export const ProposalHeader = ({
break;
}
case 'UpdateMarket': {
proposalType = t('UpdateMarket');
details = `${t('Market change')}: ${change.marketId}`;
proposalType = 'UpdateMarket';
details = (
<>
<span>{t('Market change')}:</span>{' '}
<span>{truncateMiddle(change.marketId)}</span>
</>
);
break;
}
case 'NewAsset': {
proposalType = t('NewAsset');
proposalType = 'NewAsset';
details = (
<>
{t('Symbol')}: {change.symbol}.{' '}
<Lozenge>
{change.source.__typename === 'ERC20' &&
`ERC20 ${change.source.contractAddress}`}
{change.source.__typename === 'BuiltinAsset' &&
`${t('Max faucet amount mint')}: ${
change.source.maxFaucetAmountMint
}`}
</Lozenge>
<span>{t('Symbol')}:</span> <Lozenge>{change.symbol}.</Lozenge>{' '}
{change.source.__typename === 'ERC20' && (
<>
<span>{t('ERC20ContractAddress')}:</span>{' '}
<Lozenge>{change.source.contractAddress}</Lozenge>
</>
)}{' '}
{change.source.__typename === 'BuiltinAsset' && (
<>
<span>{t('MaxFaucetAmountMint')}:</span>{' '}
<Lozenge>{change.source.maxFaucetAmountMint}</Lozenge>
</>
)}
</>
);
break;
}
case 'UpdateNetworkParameter': {
proposalType = t('NetworkParameter');
const parametersClasses = 'font-mono leading-none';
proposalType = 'NetworkParameter';
details = (
<>
<span className={`${parametersClasses} mr-2`}>
{change.networkParameter.key}
</span>{' '}
{t('to')}{' '}
<span className={`${parametersClasses} ml-2`}>
{change.networkParameter.value}
<span>{t('Change')}:</span>{' '}
<Lozenge>{change.networkParameter.key}</Lozenge>{' '}
<span>{t('to')}</span>{' '}
<span className="whitespace-nowrap">
<Lozenge>{change.networkParameter.value}</Lozenge>
</span>
</>
);
break;
}
case 'NewFreeform': {
proposalType = t('Freeform');
details = `${t('FreeformProposal')}: ${proposal?.id}`;
proposalType = 'Freeform';
details = <span />;
break;
}
case 'UpdateAsset': {
proposalType = t('UpdateAsset');
proposalType = 'UpdateAsset';
details = (
<>
`${t('Update asset')}`;
<Lozenge>{change.assetId}</Lozenge>
<span>{t('AssetID')}:</span>{' '}
<Lozenge>{truncateMiddle(change.assetId)}</Lozenge>
</>
);
break;
@@ -104,9 +117,9 @@ export const ProposalHeader = ({
}
return (
<div className="text-sm mb-2">
<>
<div data-testid="proposal-title">
{useSubHeading ? (
{isListItem ? (
<header>
<SubHeading title={titleContent || t('Unknown proposal')} />
</header>
@@ -116,18 +129,39 @@ export const ProposalHeader = ({
</div>
<div className="flex items-center gap-2 mb-4">
{proposalType && (
<div data-testid="proposal-type">
<Lozenge variant={Intent.None}>{proposalType}</Lozenge>
</div>
)}
<div data-testid="proposal-type">
<ProposalInfoLabel variant="secondary">
{t(`${proposalType}`)}
</ProposalInfoLabel>
</div>
{description && (
<div data-testid="proposal-description">{description}</div>
)}
<div data-testid="proposal-status">
<CurrentProposalState proposal={proposal} />
</div>
</div>
{details && <div data-testid="proposal-details">{details}</div>}
</div>
{details && (
<div data-testid="proposal-details" className="break-words my-10">
{details}
</div>
)}
{description && !isListItem && (
<div data-testid="proposal-description">
{/*<div className="uppercase mr-2">{t('ProposalDescription')}:</div>*/}
<SubHeading title={t('ProposalDescription')} />
<ReactMarkdown
className="react-markdown-container"
/* Prevents HTML embedded in the description from rendering */
skipHtml={true}
/* Stops users embedding images which could be used for tracking */
disallowedElements={['img']}
linkTarget="_blank"
>
{description}
</ReactMarkdown>
</div>
)}
</>
);
};
@@ -0,0 +1 @@
export * from './proposal-info-label';
@@ -0,0 +1,35 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
export type ProposalInfoLabelVariant =
| 'primary'
| 'secondary'
| 'tertiary'
| 'highlight';
const base = 'rounded-md px-2 py-1 font-alpha';
const primary = 'bg-vega-light-150 text-black';
const secondary = 'bg-vega-dark-200 text-white';
const tertiary = 'bg-vega-dark-150 text-white';
const highlight = 'bg-vega-yellow text-black';
const getClassname = (variant: ProposalInfoLabelVariant) => {
return classNames(base, {
[primary]: variant === 'primary',
[secondary]: variant === 'secondary',
[tertiary]: variant === 'tertiary',
[highlight]: variant === 'highlight',
});
};
interface ProposalInfoLabelProps {
children: ReactNode;
variant?: ProposalInfoLabelVariant;
}
export const ProposalInfoLabel = ({
children,
variant = 'primary',
}: ProposalInfoLabelProps) => {
return <div className={getClassname(variant)}>{children}</div>;
};
@@ -1,8 +1,10 @@
import { useTranslation } from 'react-i18next';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import type { PartialDeep } from 'type-fest';
import type * as Schema from '@vegaprotocol/types';
import { useState } from 'react';
import classnames from 'classnames';
export const ProposalTermsJson = ({
terms,
@@ -10,10 +12,26 @@ export const ProposalTermsJson = ({
terms: PartialDeep<Schema.ProposalTerms>;
}) => {
const { t } = useTranslation();
const [showDetails, setShowDetails] = useState(false);
const showDetailsIconClasses = classnames('mb-4', {
'rotate-180': showDetails,
});
return (
<section>
<SubHeading title={t('proposalTerms')} />
<SyntaxHighlighter data={terms} />
<button
onClick={() => setShowDetails(!showDetails)}
data-testid="proposal-terms-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalTerms')} />
<div className={showDetailsIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDetails && <SyntaxHighlighter data={terms} />}
</section>
);
};
@@ -1,4 +1,4 @@
import { render, screen } from '@testing-library/react';
import { render, screen, fireEvent } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { ProposalVotesTable } from './proposal-votes-table';
@@ -46,6 +46,7 @@ describe('Proposal Votes Table', () => {
it('should show vote breakdown fields, excluding custom update market fields', () => {
renderComponent();
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
expect(screen.getByText('Expected to pass')).toBeInTheDocument();
expect(screen.getByText('Token majority met')).toBeInTheDocument();
expect(screen.getByText('Token participation met')).toBeInTheDocument();
@@ -55,7 +56,7 @@ describe('Proposal Votes Table', () => {
expect(screen.getByText('Participation required')).toBeInTheDocument();
expect(screen.getByText('Majority Required')).toBeInTheDocument();
expect(screen.getByText('Number of voting parties')).toBeInTheDocument();
expect(screen.getByText('Total yes tokens')).toBeInTheDocument();
expect(screen.getByText('Total tokens voted')).toBeInTheDocument();
expect(
screen.getByText('Total tokens voted percentage')
).toBeInTheDocument();
@@ -70,13 +71,14 @@ describe('Proposal Votes Table', () => {
it('displays different breakdown fields for update market proposal', () => {
renderComponent(updateMarketProposal, updateMarketProposalType);
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
expect(screen.getByText('Liquidity majority met')).toBeInTheDocument();
expect(screen.getByText('Liquidity participation met')).toBeInTheDocument();
expect(
screen.getByText('Liquidity shares for proposal')
).toBeInTheDocument();
expect(screen.queryByText('Number of voting parties')).toBeNull();
expect(screen.queryByText('Total yes tokens')).toBeNull();
expect(screen.queryByText('Total tokens voted')).toBeNull();
expect(screen.queryByText('Total tokens voted percentage')).toBeNull();
expect(screen.queryByText('Number of votes for')).toBeNull();
expect(screen.queryByText('Number of votes against')).toBeNull();
@@ -86,6 +88,7 @@ describe('Proposal Votes Table', () => {
it('displays if an update market proposal will pass by token vote', () => {
renderComponent(updateMarketProposal, updateMarketProposalType);
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
expect(screen.getByText('👍 by token vote')).toBeInTheDocument();
});
@@ -110,6 +113,7 @@ describe('Proposal Votes Table', () => {
}),
updateMarketProposalType
);
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
expect(screen.getByText('👍 by liquidity vote')).toBeInTheDocument();
});
});
@@ -1,9 +1,12 @@
import classnames from 'classnames';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
KeyValueTable,
KeyValueTableRow,
Thumbs,
RoundedWrapper,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { formatNumber, formatNumberPercentage } from '@vegaprotocol/utils';
import { SubHeading } from '../../../../components/heading';
@@ -26,6 +29,7 @@ export const ProposalVotesTable = ({
const {
appState: { totalSupply },
} = useAppState();
const [showDetails, setShowDetails] = useState(false);
const {
willPassByTokenVote,
willPassByLPVote,
@@ -53,113 +57,130 @@ export const ProposalVotesTable = ({
? t('byTokenVote')
: t('byLiquidityVote');
const showDetailsIconClasses = classnames('mb-4', {
'rotate-180': showDetails,
});
return (
<>
<SubHeading title={t('voteBreakdown')} />
<RoundedWrapper>
<KeyValueTable
data-testid="proposal-votes-table"
numerical={true}
headingLevel={4}
>
<KeyValueTableRow>
{t('expectedToPass')}
{isUpdateMarket ? (
updateMarketWillPass ? (
<Thumbs up={true} text={updateMarketVotePassMethod} />
) : (
<Thumbs up={false} />
)
) : willPassByTokenVote ? (
<Thumbs up={true} />
) : (
<Thumbs up={false} />
)}
</KeyValueTableRow>
<KeyValueTableRow>
{t('majorityMet')}
{majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
{isUpdateMarket && (
<button
onClick={() => setShowDetails(!showDetails)}
data-testid="vote-breakdown-toggle"
>
<div className="flex items-center gap-3">
<SubHeading title={t('voteBreakdown')} />
<div className={showDetailsIconClasses}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDetails && (
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
<KeyValueTable
data-testid="proposal-votes-table"
numerical={true}
headingLevel={4}
>
<KeyValueTableRow>
{t('majorityLPMet')}
{majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('participationMet')}
{participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('participationLPMet')}
{participationLPMet ? (
{t('expectedToPass')}
{isUpdateMarket ? (
updateMarketWillPass ? (
<Thumbs up={true} text={updateMarketVotePassMethod} />
) : (
<Thumbs up={false} />
)
) : willPassByTokenVote ? (
<Thumbs up={true} />
) : (
<Thumbs up={false} />
)}
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('tokenForProposal')}
{formatNumber(yesTokens, 2)}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('tokenLPForProposal')}
{formatNumber(yesEquityLikeShareWeight, 2)}
{t('majorityMet')}
{majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
)}
<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 && (
<>
{isUpdateMarket && (
<KeyValueTableRow>
{t('numberOfVotingParties')}
{formatNumber(totalVotes, 0)}
{t('majorityLPMet')}
{majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('participationMet')}
{participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('totalTokensVotes')}
{formatNumber(totalTokensVoted, 2)}
{t('participationLPMet')}
{participationLPMet ? (
<Thumbs up={true} />
) : (
<Thumbs up={false} />
)}
</KeyValueTableRow>
)}
<KeyValueTableRow>
{t('tokenForProposal')}
{formatNumber(yesTokens, 2)}
</KeyValueTableRow>
{isUpdateMarket && (
<KeyValueTableRow>
{t('totalTokenVotedPercentage')}
{formatNumberPercentage(totalTokensPercentage, 2)}
{t('tokenLPForProposal')}
{formatNumber(yesEquityLikeShareWeight, 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>
)}
<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>
{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,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { AsyncRenderer, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
@@ -77,8 +77,8 @@ export const Proposal = ({ proposal }: ProposalProps) => {
return (
<AsyncRenderer data={params} loading={loading} error={error}>
<section data-testid="proposal">
<ProposalHeader proposal={proposal} useSubHeading={false} />
<div className="mb-10">
<ProposalHeader proposal={proposal} isListItem={false} />
<div className="my-10">
<ProposalChangeTable proposal={proposal} />
</div>
{proposal.terms.change.__typename === 'NewAsset' &&
@@ -91,14 +91,18 @@ export const Proposal = ({ proposal }: ProposalProps) => {
/>
) : null}
<div className="mb-12">
<VoteDetails
proposal={proposal}
proposalType={proposalType}
minVoterBalance={minVoterBalance}
spamProtectionMinTokens={params?.spam_protection_voting_min_tokens}
/>
<RoundedWrapper paddingBottom={true}>
<VoteDetails
proposal={proposal}
proposalType={proposalType}
minVoterBalance={minVoterBalance}
spamProtectionMinTokens={
params?.spam_protection_voting_min_tokens
}
/>
</RoundedWrapper>
</div>
<div className="mb-10">
<div className="mb-4">
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
</div>
<ProposalTermsJson terms={proposal.terms} />
@@ -97,7 +97,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Enacted');
expect(screen.getByTestId('vote-details')).toHaveTextContent(
format(lastWeek, DATE_FORMAT_DETAILED)
);
@@ -113,7 +112,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Passed');
expect(screen.getByTestId('vote-details')).toHaveTextContent(
`Enacts on ${format(nextWeek, DATE_FORMAT_DETAILED)}`
);
@@ -128,9 +126,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent(
'Waiting for node vote'
);
expect(screen.getByTestId('vote-details')).toHaveTextContent(
`Enacts on ${format(nextWeek, DATE_FORMAT_DETAILED)}`
);
@@ -221,7 +216,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-details')).toHaveTextContent(
'5 minutes left to vote'
);
@@ -236,7 +230,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-details')).toHaveTextContent(
'5 hours left to vote'
);
@@ -251,7 +244,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-details')).toHaveTextContent(
'5 days left to vote'
);
@@ -268,10 +260,7 @@ describe('Proposals list item details', () => {
networkParamsQueryMock,
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_YES),
]);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(await screen.findByText('You voted')).toBeInTheDocument();
expect(await screen.findByText('For')).toBeInTheDocument();
expect(await screen.findByText('You voted For')).toBeInTheDocument();
});
it('Renders proposal state: Open - user voted against', async () => {
@@ -285,9 +274,7 @@ describe('Proposals list item details', () => {
networkParamsQueryMock,
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_NO),
]);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(await screen.findByText('You voted')).toBeInTheDocument();
expect(await screen.findByText('Against')).toBeInTheDocument();
expect(await screen.findByText('You voted Against')).toBeInTheDocument();
});
it('Renders proposal state: Open - participation not reached', () => {
@@ -303,7 +290,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Participation not reached'
);
@@ -322,7 +308,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Majority not reached'
);
@@ -342,7 +327,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
expect(screen.getByTestId('vote-status')).toHaveTextContent('Set to pass');
});
@@ -359,7 +343,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined');
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Participation not reached'
);
@@ -378,7 +361,6 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined');
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Majority not reached'
);
@@ -395,7 +377,6 @@ describe('Proposals list item details', () => {
ProposalRejectionReason.PROPOSAL_ERROR_INVALID_FUTURE_PRODUCT,
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Rejected');
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Invalid future product'
);
@@ -1,11 +1,8 @@
import { Link } from 'react-router-dom';
import { Button, Icon } from '@vegaprotocol/ui-toolkit';
import { Button } from '@vegaprotocol/ui-toolkit';
import { useVoteInformation } from '../../hooks';
import { useUserVote } from '../vote-details/use-user-vote';
import {
StatusPass,
StatusFail,
} from '../current-proposal-status/current-proposal-status';
import { StatusPass } from '../current-proposal-status/current-proposal-status';
import { format, formatDistanceToNowStrict } from 'date-fns';
import { useTranslation } from 'react-i18next';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
@@ -22,7 +19,7 @@ const MajorityNotReached = () => {
const { t } = useTranslation();
return (
<>
{t('Majority')} <StatusFail>{t('not reached')}</StatusFail>
{t('Majority')} {t('not reached')}
</>
);
};
@@ -30,7 +27,7 @@ const ParticipationNotReached = () => {
const { t } = useTranslation();
return (
<>
{t('Participation')} <StatusFail>{t('not reached')}</StatusFail>
{t('Participation')} {t('not reached')}
</>
);
};
@@ -57,17 +54,11 @@ export const ProposalsListItemDetails = ({
? t('byTokenVote')
: t('byLPVote');
let proposalStatus: ReactNode;
let voteDetails: ReactNode;
let voteStatus: ReactNode;
switch (state) {
case ProposalState.STATE_ENACTED: {
proposalStatus = (
<>
{t('voteState_Enacted')} <Icon name={'tick'} />
</>
);
voteDetails = proposal?.terms.enactmentDatetime && (
<>
{format(
@@ -79,11 +70,6 @@ export const ProposalsListItemDetails = ({
break;
}
case ProposalState.STATE_PASSED: {
proposalStatus = (
<>
{t('voteState_Passed')} <Icon name={'tick'} />
</>
);
voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && (
<>
{t('toEnactOn')}{' '}
@@ -97,11 +83,6 @@ export const ProposalsListItemDetails = ({
break;
}
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
proposalStatus = (
<>
{t('voteState_WaitingForNodeVote')} <Icon name={'time'} />
</>
);
voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && (
<>
{t('toEnactOn')}{' '}
@@ -115,19 +96,14 @@ export const ProposalsListItemDetails = ({
break;
}
case ProposalState.STATE_OPEN: {
proposalStatus = (
<>
{t('voteState_Open')} <Icon name={'hand'} />
</>
);
voteDetails = (voteState === 'Yes' && (
<>
{t('youVoted')} <StatusPass>{t('voteState_Yes')}</StatusPass>
{t('youVoted')} {t('voteState_Yes')}
</>
)) ||
(voteState === 'No' && (
<>
{t('youVoted')} <StatusFail>{t('voteState_No')}</StatusFail>
{t('youVoted')} {t('voteState_No')}
</>
)) || (
<>
@@ -148,40 +124,29 @@ export const ProposalsListItemDetails = ({
</>
) : (
<>
{t('Set to')} <StatusFail>{t('fail')}</StatusFail>
{t('Set to')} {t('fail')}
</>
))) ||
(!participationMet && <ParticipationNotReached />) ||
(!majorityMet && <MajorityNotReached />) ||
(willPassByTokenVote ? (
<>
{t('Set to')} <StatusPass>{t('pass')}</StatusPass>
{t('Set to')} {t('pass')}
</>
) : (
<>
{t('Set to')} <StatusFail>{t('fail')}</StatusFail>
{t('Set to')} {t('fail')}
</>
));
break;
}
case ProposalState.STATE_DECLINED: {
proposalStatus = (
<>
{t('voteState_Declined')} <Icon name={'cross'} />
</>
);
voteStatus =
(!participationMet && <ParticipationNotReached />) ||
(!majorityMet && <MajorityNotReached />);
break;
}
case ProposalState.STATE_REJECTED: {
proposalStatus = (
<>
<StatusFail>{t('voteState_Rejected')}</StatusFail>{' '}
<Icon name={'warning-sign'} />
</>
);
voteStatus = proposal?.rejectionReason && (
<>{t(ProposalRejectionReasonMapping[proposal.rejectionReason])}</>
);
@@ -190,16 +155,10 @@ export const ProposalsListItemDetails = ({
}
return (
<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>
<div className="grid grid-cols-[1fr_auto] mt-4 items-start gap-2 text-sm">
{voteDetails && (
<div
className="col-start-1 row-start-2 text-neutral-500"
className="col-start-1 row-start-2 text-vega-light-300"
data-testid="vote-details"
>
{voteDetails}
@@ -216,9 +175,7 @@ export const ProposalsListItemDetails = ({
{proposal?.id && (
<div className="col-start-2 row-start-2 justify-self-end">
<Link to={`${Routes.PROPOSALS}/${proposal.id}`}>
<Button data-testid="view-proposal-btn" size="sm">
{t('View')}
</Button>
<Button data-testid="view-proposal-btn">{t('View')}</Button>
</Link>
</div>
)}
@@ -106,7 +106,7 @@ export const ProposalsList = ({
{proposals.length > 0 && (
<ProposalsListFilter setFilterString={setFilterString} />
)}
<section className="-mx-4 p-4 mb-8 bg-neutral-800">
<section className="-mx-4 p-4 mb-8 bg-vega-dark-100">
<SubHeading title={t('openProposals')} />
{sortedProposals.open.length > 0 ||
sortedProtocolUpgradeProposals.open.length > 0 ? (
@@ -3,15 +3,15 @@ import { Link } from 'react-router-dom';
import {
Button,
Icon,
Intent,
Lozenge,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { stripFullStops } from '@vegaprotocol/utils';
import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types';
import { SubHeading } from '../../../../components/heading';
import type { ReactNode } from 'react';
import { ProposalInfoLabel } from '../proposal-info-label';
import Routes from '../../../routes';
import type { ReactNode } from 'react';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
interface ProtocolProposalsListItemProps {
@@ -29,30 +29,30 @@ export const ProtocolUpgradeProposalsListItem = ({
switch (proposal.status) {
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED:
proposalStatusIcon = (
<div data-testid="protocol-upgrade-proposal-status-icon-rejected">
<span data-testid="protocol-upgrade-proposal-status-icon-rejected">
<Icon name={'cross'} />
</div>
</span>
);
break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING:
proposalStatusIcon = (
<div data-testid="protocol-upgrade-proposal-status-icon-pending">
<span data-testid="protocol-upgrade-proposal-status-icon-pending">
<Icon name={'time'} />
</div>
</span>
);
break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED:
proposalStatusIcon = (
<div data-testid="protocol-upgrade-proposal-status-icon-approved">
<span data-testid="protocol-upgrade-proposal-status-icon-approved">
<Icon name={'tick'} />
</div>
</span>
);
break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED:
proposalStatusIcon = (
<div data-testid="protocol-upgrade-proposal-status-icon-unspecified">
<span data-testid="protocol-upgrade-proposal-status-icon-unspecified">
<Icon name={'disable'} />
</div>
</span>
);
break;
}
@@ -71,18 +71,28 @@ export const ProtocolUpgradeProposalsListItem = ({
</div>
<div className="text-sm">
<div
data-testid="protocol-upgrade-proposal-type"
className="flex items-center gap-2 mb-4"
>
<Lozenge variant={Intent.Success}>{t('networkUpgrade')}</Lozenge>
<div className="flex gap-2">
<div
data-testid="protocol-upgrade-proposal-type"
className="flex items-center gap-2 mb-4"
>
<ProposalInfoLabel variant="highlight">
{t('networkUpgrade')}
</ProposalInfoLabel>
</div>
<div data-testid="protocol-upgrade-proposal-status">
<ProposalInfoLabel>
{t(`${proposal.status}`)} {proposalStatusIcon}
</ProposalInfoLabel>
</div>
</div>
<div
data-testid="protocol-upgrade-proposal-release-tag"
className="mb-2"
>
<span className="pr-2">{t('vegaReleaseTag')}</span>
<span>{t('vegaReleaseTag')}:</span>{' '}
<Lozenge>{proposal.vegaReleaseTag}</Lozenge>
</div>
@@ -90,30 +100,18 @@ export const ProtocolUpgradeProposalsListItem = ({
data-testid="protocol-upgrade-proposal-block-height"
className="mb-2"
>
<span className="pr-2">{t('upgradeBlockHeight')}</span>
<span>{t('upgradeBlockHeight')}:</span>{' '}
<Lozenge>{proposal.upgradeBlockHeight}</Lozenge>
</div>
<div className="grid grid-cols-[1fr_auto] mt-3 items-start gap-2">
<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">
<div className="grid grid-cols-1 mt-3">
<div className="justify-self-end">
<Link
to={`${Routes.PROPOSALS}/protocol-upgrade/${stripFullStops(
proposal.vegaReleaseTag
)}`}
>
<Button data-testid="view-proposal-btn" size="sm">
{t('View')}
</Button>
<Button data-testid="view-proposal-btn">{t('View')}</Button>
</Link>
</div>
</div>
@@ -107,10 +107,6 @@ export const VoteButtons = ({
);
}
if (currentStakeAvailable.isLessThanOrEqualTo(0)) {
return t('noGovernanceTokens');
}
if (minVoterBalance && spamProtectionMinTokens) {
const formattedMinVoterBalance = new BigNumber(
addDecimal(minVoterBalance, 18)
@@ -163,24 +159,30 @@ export const VoteButtons = ({
return (
<>
{changeVote || (voteState === VoteState.NotCast && proposalVotable) ? (
<div className="flex gap-4" data-testid="vote-buttons">
<div className="flex-1">
<>
{currentStakeAvailable.isLessThanOrEqualTo(0) && (
<p data-testid="no-stake-available">{t('noGovernanceTokens')}</p>
)}
<div className="flex gap-4" data-testid="vote-buttons">
<Button
data-testid="vote-for"
onClick={() => submitVote(VoteValue.VALUE_YES)}
variant="primary"
disabled={currentStakeAvailable.isLessThanOrEqualTo(0)}
>
{t('voteFor')}
</Button>
</div>
<div className="flex-1">
<Button
data-testid="vote-against"
onClick={() => submitVote(VoteValue.VALUE_NO)}
variant="primary"
disabled={currentStakeAvailable.isLessThanOrEqualTo(0)}
>
{t('voteAgainst')}
</Button>
</div>
</div>
</>
) : (
(voteState === VoteState.Yes || voteState === VoteState.No) && (
<p data-testid="you-voted">
@@ -1,5 +1,6 @@
import { useTranslation } from 'react-i18next';
import { formatDistanceToNow } from 'date-fns';
import { RoundedWrapper, Icon } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { ProposalState } from '@vegaprotocol/types';
import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals';
@@ -199,10 +200,11 @@ export const VoteDetails = ({
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
<p>{t('votingThresholdInfo')}</p>
)}
{pubKey ? (
<section className="mt-10">
<SubHeading title={t('yourVote')} />
{proposal && (
<section className="mt-10">
<SubHeading title={t('castYourVote')} />
{pubKey ? (
proposal && (
<VoteButtonsContainer
voteState={voteState}
voteDatetime={voteDatetime}
@@ -214,11 +216,19 @@ export const VoteDetails = ({
submit={submit}
dialog={Dialog}
/>
)}
</section>
) : (
<ConnectToVega />
)}
)
) : (
<RoundedWrapper paddingBottom={true}>
<div className="mb-4">
<div className="flex items-center gap-2 mb-2">
<Icon name={'info-sign'} />
<div>{t('connectAVegaWalletToVote')}</div>
</div>
</div>
<ConnectToVega />
</RoundedWrapper>
)}
</section>
</section>
</>
);
@@ -34,8 +34,9 @@ export const useUserTrancheBalances = (address: string | undefined) => {
vesting.get_vested_for_tranche(address, tId),
]);
const total = toBigNum(t, decimals);
const vested = toBigNum(v, decimals);
// Convert t and v EthersBigNumbers to regular BigNumbers
const total = toBigNum(t.toString(), decimals);
const vested = toBigNum(v.toString(), decimals);
return {
id: tId,
@@ -54,7 +54,7 @@ export const WalletAssociate = ({
address,
ethereumConfig.staking_bridge_contract.address
);
const allowance = toBigNum(a, decimals);
const allowance = toBigNum(a.toString(), decimals);
setAllowance(allowance);
}
};
+3
View File
@@ -6,6 +6,7 @@ import '@testing-library/jest-dom';
import dev from './i18n/translations/dev.json';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import ResizeObserver from 'resize-observer-polyfill';
// Set up i18n instance so that components have the correct default
// en translations
@@ -22,3 +23,5 @@ i18n.use(initReactI18next).init({
ns: ['translations'],
defaultNS: 'translations',
});
global.ResizeObserver = ResizeObserver;
+41
View File
@@ -40,6 +40,11 @@
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 */
.ag-theme-balham-dark {
--ag-background-color: theme(colors.black);
@@ -58,3 +63,39 @@
.validators-table .ag-theme-balham-dark *:hover {
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
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\"}
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.xyz/query
NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
NX_VEGA_ENV=MAINNET
+190 -43
View File
@@ -48,8 +48,8 @@
"tranche_start": "2023-04-20T00:00:00.000Z",
"tranche_end": "2023-05-20T00:00:00.000Z",
"total_added": "19242.125",
"total_removed": "1214.9959502191275",
"locked_amount": "9295.5944273726850283975",
"total_removed": "1523.8177488329475",
"locked_amount": "6891.9471601080244395475",
"deposits": [
{
"amount": "188",
@@ -223,6 +223,11 @@
}
],
"withdrawals": [
{
"amount": "308.82179861382",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
"tx": "0x2b3571c143ecebddf91fb62f402d516d51110edfe37b13200a6e5cf682dc5bb0"
},
{
"amount": "202.093666077975",
"user": "0x1447Efc62d5077d7AbB20492dF831Dd6c9Eb1756",
@@ -351,6 +356,12 @@
}
],
"withdrawals": [
{
"amount": "308.82179861382",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
"tranche_id": 56,
"tx": "0x2b3571c143ecebddf91fb62f402d516d51110edfe37b13200a6e5cf682dc5bb0"
},
{
"amount": "195.89040769089",
"user": "0xDd7a98557586ce21f770662319C2047C5a3bD605",
@@ -359,8 +370,8 @@
}
],
"total_tokens": "914.25",
"withdrawn_tokens": "195.89040769089",
"remaining_tokens": "718.35959230911"
"withdrawn_tokens": "504.71220630471",
"remaining_tokens": "409.53779369529"
},
{
"address": "0x9573BDF7FfC5519912d293e4D1f750eab2E471E7",
@@ -865,10 +876,15 @@
"tranche_id": 54,
"tranche_start": "2023-04-06T00:00:00.000Z",
"tranche_end": "2023-05-06T00:00:00.000Z",
"total_added": "14520",
"total_removed": "5079.76864115505",
"locked_amount": "238.4036111111108718",
"total_added": "14610",
"total_removed": "6141.45090157707",
"locked_amount": "0",
"deposits": [
{
"amount": "90",
"user": "0x05659B08a8079E003eE37F26c29e52532728c034",
"tx": "0x83cb92910e0725b82b3459e025260c448f931224f7cb00a376351dbd75ae2733"
},
{
"amount": "111",
"user": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241",
@@ -1021,6 +1037,46 @@
}
],
"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",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
@@ -1068,6 +1124,28 @@
}
],
"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",
"deposits": [
@@ -1078,10 +1156,17 @@
"tx": "0x1229e077f48b796678436bfa8143cea3ae71df58b589ac922be205c4482be235"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "111",
"user": "0x90Cf7B9958C3BbBCddAb52Ecc408535D4f3C4241",
"tranche_id": 54,
"tx": "0xdfae5add3e12eed919c0481d9289aa33688884db68ca90332d1df31107e4fbc6"
}
],
"total_tokens": "111",
"withdrawn_tokens": "0",
"remaining_tokens": "111"
"withdrawn_tokens": "111",
"remaining_tokens": "0"
},
{
"address": "0x83D7eD53E7CB97b542F1F71f40561d51F8019C8B",
@@ -1326,6 +1411,12 @@
}
],
"withdrawals": [
{
"amount": "191.85071991228",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
"tranche_id": 54,
"tx": "0x1438ad7d4c51ec6cb9b8f9d67b05583915099427035179baf996fd208fd76603"
},
{
"amount": "106.53500000286",
"user": "0x6F32AA5A6198329c16e438512F992a0548C856f9",
@@ -1364,8 +1455,8 @@
}
],
"total_tokens": "858",
"withdrawn_tokens": "666.14928008772",
"remaining_tokens": "191.85071991228"
"withdrawn_tokens": "858",
"remaining_tokens": "0"
},
{
"address": "0x0bBf7580e036eA5D69ABe679CC90117EeC2e3dc1",
@@ -1393,6 +1484,18 @@
}
],
"withdrawals": [
{
"amount": "116.83154050974",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
"tranche_id": 54,
"tx": "0x130c439ea764588c13cd825ae89767bea04064bd3d76b762b7148e5373857b9b"
},
{
"amount": "0",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
"tranche_id": 54,
"tx": "0x9be84231ff156bc8b8de9a99250f03b8ebf9d59c252e3b778f10051489e5758a"
},
{
"amount": "60.16845949026",
"user": "0x15024E62134A8BFFCce11f5ce58CeCDe853038D7",
@@ -1401,8 +1504,8 @@
}
],
"total_tokens": "177",
"withdrawn_tokens": "60.16845949026",
"remaining_tokens": "116.83154050974"
"withdrawn_tokens": "177",
"remaining_tokens": "0"
},
{
"address": "0xAc1c2783d64c29CDB79608f5B66a37Fd1ea35dD6",
@@ -1414,10 +1517,17 @@
"tx": "0x99aeaedef27b5fb693485f4e21d434e343b30fa100945a4ff65386de8801c87e"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "111",
"user": "0xAc1c2783d64c29CDB79608f5B66a37Fd1ea35dD6",
"tranche_id": 54,
"tx": "0x7195dc5808870b0f00824c6593eda1e994976189cf3373cf84c5dae7a60d8b62"
}
],
"total_tokens": "111",
"withdrawn_tokens": "0",
"remaining_tokens": "111"
"withdrawn_tokens": "111",
"remaining_tokens": "0"
},
{
"address": "0x8D416A61bCccf4E6aF5598302BC4e41f97401652",
@@ -1444,10 +1554,17 @@
"tx": "0xcb0f255003872ac506798efa97744868e11ceab2f2f03d96da605d8674f783f6"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "132",
"user": "0x9E53F72210479BF46aE7ABF77c48B0d611b3c3dB",
"tranche_id": 54,
"tx": "0x1dbcf713b48965a82aa2e17cb3e7db9a491668d859d408a39c8a74b0ea860b6b"
}
],
"total_tokens": "132",
"withdrawn_tokens": "0",
"remaining_tokens": "132"
"withdrawn_tokens": "132",
"remaining_tokens": "0"
},
{
"address": "0xC0f02AA8bA8b509D223C9De6108AFA1C03f03341",
@@ -1474,10 +1591,17 @@
"tx": "0xf6467d54c2de5c5d21fd3ac174ac6bf51b6fc675cf81dedcb143679f28caa46a"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "309",
"user": "0x21770cCAb229Bd1509609fFFa35bEB16b73A657c",
"tranche_id": 54,
"tx": "0x264f4b3c5dc43cd5afaa5954b50ba7885ce3091b11ae51ba19b8a5851a5a4fc7"
}
],
"total_tokens": "309",
"withdrawn_tokens": "0",
"remaining_tokens": "309"
"withdrawn_tokens": "309",
"remaining_tokens": "0"
},
{
"address": "0x0629D3C608e1E8e9B88a1B00e1422e4B33276a98",
@@ -4737,7 +4861,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "86666.297",
"total_removed": "0",
"locked_amount": "50692.0833719598561288655",
"locked_amount": "49802.2753121154213651374",
"deposits": [
{
"amount": "86666.297",
@@ -4803,7 +4927,7 @@
"tranche_end": "2023-06-01T00:00:00.000Z",
"total_added": "2500",
"total_removed": "0",
"locked_amount": "363.908920940171075",
"locked_amount": "312.4325905575906",
"deposits": [
{
"amount": "2500",
@@ -4836,7 +4960,7 @@
"tranche_end": "2023-11-01T00:00:00.000Z",
"total_added": "15000.000000000000015",
"total_removed": "0",
"locked_amount": "14632.5464221014495146325464221014495",
"locked_amount": "14327.0455917874395143270455917874395",
"deposits": [
{
"amount": "1.5e-14",
@@ -4924,7 +5048,7 @@
"tranche_end": "2023-09-01T00:00:00.000Z",
"total_added": "17500",
"total_removed": "0",
"locked_amount": "11269.67372433574875",
"locked_amount": "10913.25608896940375",
"deposits": [
{
"amount": "12500",
@@ -5191,7 +5315,7 @@
"tranche_end": "2023-08-01T00:00:00.000Z",
"total_added": "37500",
"total_removed": "18077.0118744",
"locked_amount": "18126.91355893186125",
"locked_amount": "17350.5026089625535",
"deposits": [
{
"amount": "7500",
@@ -5610,7 +5734,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45",
"total_removed": "0",
"locked_amount": "50645.8332242054391795015",
"locked_amount": "49756.837002255673748325",
"deposits": [
{
"amount": "129999.45",
@@ -5643,7 +5767,7 @@
"tranche_end": "2024-04-01T00:00:00.000Z",
"total_added": "54144.7663",
"total_removed": "0",
"locked_amount": "49039.85711135509392504979",
"locked_amount": "48485.46842731635829207292",
"deposits": [
{
"amount": "54144.7663",
@@ -5676,7 +5800,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "62600",
"total_removed": "0",
"locked_amount": "20665.30095129376038",
"locked_amount": "20022.5830035514954",
"deposits": [
{
"amount": "10000",
@@ -5869,7 +5993,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000",
"total_removed": "0",
"locked_amount": "1842.3639649923895",
"locked_amount": "1791.028665651953",
"deposits": [
{
"amount": "5000",
@@ -6938,7 +7062,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38",
"total_removed": "1709370.7872515768348",
"locked_amount": "146119.4950805440611341152",
"locked_amount": "126202.1342632826843379468",
"deposits": [
{
"amount": "1852091.69",
@@ -40810,7 +40934,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671",
"total_removed": "715655.108029600523393",
"locked_amount": "249036.115263241476836903328",
"locked_amount": "218430.066175628697078272948",
"deposits": [
{
"amount": "1998.95815",
@@ -42202,8 +42326,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001",
"total_removed": "870712.39693462448509352",
"locked_amount": "6182753.66071744239145030977234379973803727",
"total_removed": "871680.07831804700259352",
"locked_amount": "6074226.5757489411253544394273819693295485",
"deposits": [
{
"amount": "16249.93",
@@ -42707,6 +42831,11 @@
}
],
"withdrawals": [
{
"amount": "967.6813834225175",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x625e0c8fb8d5e2dd7fef0359bb396571f7d8aed05a601ddf0f6e6577edb26a13"
},
{
"amount": "10150.87581603206683",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
@@ -44875,6 +45004,12 @@
}
],
"withdrawals": [
{
"amount": "967.6813834225175",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tranche_id": 2,
"tx": "0x625e0c8fb8d5e2dd7fef0359bb396571f7d8aed05a601ddf0f6e6577edb26a13"
},
{
"amount": "913.910324501590625",
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
@@ -46431,8 +46566,8 @@
}
],
"total_tokens": "259998.8875",
"withdrawn_tokens": "158622.840720963906375",
"remaining_tokens": "101376.046779036093625"
"withdrawn_tokens": "159590.522104386423875",
"remaining_tokens": "100408.365395613576125"
},
{
"address": "0x89051CAb67Bc7F8CC44F7e270c6EDaf1EC57676c",
@@ -58934,8 +59069,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996",
"total_removed": "44110.3307271943416",
"locked_amount": "39461.19601458522069398354033484",
"total_removed": "44479.0535455583416",
"locked_amount": "34611.49258501772999518148756976",
"deposits": [
{
"amount": "3000",
@@ -65554,6 +65689,11 @@
}
],
"withdrawals": [
{
"amount": "368.722818364",
"user": "0x0436E2E4EA467f9958403dc56635404EE1bb1E94",
"tx": "0x97f5b5ec5ac839a8d182b9267b5d052cb70f9a18b648874337e3d5360b21a5fb"
},
{
"amount": "182.662252664",
"user": "0xb1169C6daAc76bAcaf0D8f87641Fc38fbabe569F",
@@ -81763,10 +81903,17 @@
"tx": "0xb1425d9b0d5f10c5b06236d6f9d61ebddd21c4027e0f41deb5ec227a1753c715"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "368.722818364",
"user": "0x0436E2E4EA467f9958403dc56635404EE1bb1E94",
"tranche_id": 5,
"tx": "0x97f5b5ec5ac839a8d182b9267b5d052cb70f9a18b648874337e3d5360b21a5fb"
}
],
"total_tokens": "400",
"withdrawn_tokens": "0",
"remaining_tokens": "400"
"withdrawn_tokens": "368.722818364",
"remaining_tokens": "31.277181636"
},
{
"address": "0x6672F8789C54b49C596E6e7Ca3A72726Fc4C400E",
+48 -49
View File
@@ -9,6 +9,7 @@ import {
import { isBefore, isAfter, addSeconds, subSeconds } from 'date-fns';
import { createOrder } from '../support/create-order';
import { connectEthereumWallet } from '../support/ethereum-wallet';
import { selectAsset } from '../support/helpers';
const orderSize = 'size';
const orderType = 'type';
@@ -22,15 +23,13 @@ const assetSelectField = 'select[name="asset"]';
const amountField = 'input[name="amount"]';
const txTimeout = Cypress.env('txTimeout');
const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
const btcName =
'BTC (local)5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c - tBTC';
const vegaName =
'Vegab4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b - VEGA';
const btcName = 0;
const vegaName = 4;
const btcSymbol = 'tBTC';
const vegaSymbol = 'VEGA';
const usdcSymbol = 'fUSDC';
const toastContent = 'toast-content';
const ordersTab = 'Orders';
const openOrdersTab = 'Open';
const depositsTab = 'Deposits';
const collateralTab = 'Collateral';
const toastCloseBtn = 'toast-close';
@@ -50,6 +49,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.get('@markets').then((markets) => {
cy.wrap(markets[0]).as('market');
});
cy.visit('/#/portfolio');
});
beforeEach(() => {
@@ -73,7 +73,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
selectAsset(btcName);
cy.getByTestId('approve-default').should(
'contain.text',
`Before you can make a deposit of your chosen asset, ${btcSymbol}, you need to approve its use in your Ethereum wallet`
@@ -81,6 +81,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId(approveSubmit).click();
cy.getByTestId('approve-pending').should('exist');
cy.getByTestId('approve-confirmed').should('exist');
cy.get(amountField).focus();
cy.get(amountField).clear().type('10');
cy.getByTestId(depositSubmit).click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -115,7 +116,6 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
});
it('can key to key transfers', function () {
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(collateralTab).click();
@@ -147,8 +147,8 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
selectAsset(0);
cy.get(amountField).focus();
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -170,7 +170,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Error occurredprocessing response error'
'Error occurredcannot estimate gas'
);
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId(completeWithdrawalBtn).should(
@@ -180,7 +180,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
});
});
describe('capsule', { tags: '@slow' }, () => {
describe('capsule', { tags: '@slow', testIsolation: true }, () => {
before(() => {
cy.updateCapsuleMultiSig();
});
@@ -231,7 +231,7 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+${order.size} @ ${order.price}.00 ${usdcSymbol}`,
`Order submittedYour transaction has been confirmed View in block explorerSubmit order - activeTEST.24h+${order.size} @ ${order.price}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
@@ -242,9 +242,9 @@ describe('capsule', { tags: '@slow' }, () => {
.get(`[data-testid="bid-vol-${rawPrice}"]`)
.should('contain.text', order.size);
cy.getByTestId(ordersTab).click();
cy.getByTestId(openOrdersTab).click();
cy.getByTestId('edit', txTimeout).should('contain.text', 'Edit');
cy.getByTestId('tab-orders').within(() => {
cy.getByTestId('tab-open-orders').within(() => {
cy.get('.ag-center-cols-container')
.children()
.first()
@@ -283,7 +283,9 @@ describe('capsule', { tags: '@slow' }, () => {
});
});
it('can edit order', function () {
cy.getByTestId(ordersTab).click();
const market = this.market;
cy.visit(`/#/markets/${market.id}`);
cy.getByTestId(openOrdersTab).click();
cy.getByTestId('edit').first().should('be.visible').click();
cy.getByTestId('dialog-title').should('contain.text', 'Edit order');
cy.get('#limitPrice').focus().clear().type(newPrice);
@@ -291,12 +293,12 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`,
`Order submittedYour transaction has been confirmed View in block explorerEdit order - activeTEST.24h+${size} @ ${price}.00 ${usdcSymbol}+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId(ordersTab).click();
cy.getByTestId(openOrdersTab).click();
cy.get('.ag-center-cols-container')
.children()
.first()
@@ -307,18 +309,20 @@ describe('capsule', { tags: '@slow' }, () => {
checkIfDataAndTimeOfCreationAndUpdateIsEqual(orderUpdatedAt);
});
});
// comment because of bug #2695
it.skip('can cancel order', function () {
cy.getByTestId(ordersTab).click();
it('can cancel order', function () {
const market = this.market;
cy.visit(`/#/markets/${market.id}`);
cy.getByTestId(openOrdersTab).click();
cy.getByTestId('cancel').first().click();
cy.getByTestId(toastContent).should(
'contain.text',
`ConfirmedYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+${size} @ ${newPrice}.00 ${usdcSymbol}`,
`Order cancelledYour transaction has been confirmed View in block explorerCancel order - cancelledTEST.24h+${size} @ ${newPrice}.00 ${usdcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId('tab-orders')
cy.getByTestId('Closed').click();
cy.getByTestId('tab-closed-orders')
.get('.ag-center-cols-container')
.children()
.first()
@@ -348,7 +352,7 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
selectAsset(btcName);
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
@@ -371,19 +375,15 @@ describe('capsule', { tags: '@slow' }, () => {
'contain.text',
'Transaction confirmed'
);
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Funds unlockedYour funds have been unlocked for withdrawalView in block explorerWithdraw 1.00 tBTCComplete withdrawal'
);
cy.wrap(null).then(() => {
try {
cy.getByTestId(completeWithdrawalBtn)
.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('.ag-center-cols-container')
.find('[col-id="status"]')
.eq(0, txTimeout)
.should('contain.text', 'Completed');
cy.get('[col-id="txHash"]', txTimeout)
.should('have.length.above', 1)
@@ -404,17 +404,20 @@ describe('capsule', { tags: '@slow' }, () => {
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
// comment because of bug #2819
// cy.getByTestId('withdraw-dialog-button').click();
// cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '0')
cy.getByTestId('withdraw-dialog-button').click({ force: true });
cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '7.999');
});
it('approved amount is less than deposit', function () {
// 1001-DEPO-006
// 1001-DEPO-007
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
cy.get(assetSelectField, txTimeout).select(btcName, { force: true });
connectEthereumWallet('Unknown');
selectAsset(btcName);
cy.contains('Deposits of tBTC not approved').should('not.exist');
cy.contains('Use maximum').should('be.visible');
cy.get(amountField).clear().type('20000000');
@@ -422,11 +425,7 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId(depositSubmit).click();
cy.getByTestId('input-error-text').should(
'contain.text',
'Amount is above approved amount'
);
cy.getByTestId('reapprove-default').should(
'contain.text',
'Approve again to deposit more than'
`You can't deposit more than you have in your Ethereum wallet`
);
});
@@ -440,11 +439,11 @@ describe('capsule', { tags: '@slow' }, () => {
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
selectAsset(vegaName);
cy.getByTestId('approve-submit').click();
cy.getByTestId('approve-confirmed').should(
'contain.text',
'You can now make deposits in VEGA, up to a maximum of'
'You approved deposits of up to VEGA'
);
cy.get(amountField).clear().type('10000');
cy.getByTestId('deposit-submit').click();
@@ -478,9 +477,9 @@ describe('capsule', { tags: '@slow' }, () => {
.and('contain', `${sepoliaUrl}/tx/0x`);
});
cy.getByTestId('Withdrawals').click();
cy.getByTestId('Withdrawals').click(txTimeout);
cy.getByTestId('withdraw-dialog-button').click();
cy.get(assetSelectField, txTimeout).select(vegaName, { force: true });
selectAsset(1);
cy.get(amountField).clear().type('10000');
cy.getByTestId('DELAY_TIME_value').should('have.text', '5 days');
cy.getByTestId('submit-withdrawal').click();
+195 -179
View File
@@ -14,210 +14,226 @@ const itemHeader = 'item-header';
const itemValue = 'item-value';
const marketListContent = 'popover-content';
describe('Console - market list - live env', { tags: '@live' }, () => {
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]);
describe(
'Console - market list - live env',
{ tags: '@live', testIsolation: true },
() => {
beforeEach(() => {
cy.visit('/');
});
});
it('renders correctly liquidity in trading tab', () => {
cy.getByTestId('Liquidity').click();
cy.contains('Loading').should('not.exist');
cy.contains('Something went wrong').should('not.exist');
cy.contains('Application error').should('not.exist');
cy.getByTestId('tab-liquidity').within(() => {
cy.get('[col-id="party.id"]').eq(1).should('not.be.empty');
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');
});
});
});
describe('Console - market summary - live env', { tags: '@live' }, () => {
before(() => {
cy.visit('/');
cy.getByTestId('dialog-close').click();
cy.getByTestId(marketSummaryBlock).should('be.visible');
});
it('redirects to a default market', () => {
cy.getByTestId('dialog-close').click();
cy.getByTestId(selectMarketOverlay).should('not.exist');
it('must display market name', () => {
cy.getByTestId('popover-trigger').should('not.be.empty');
});
// 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');
});
}
);
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');
describe(
'Console - market info - live env',
{ tags: '@live', testIsolation: true },
() => {
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('must see market price', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketPrice).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Price');
cy.getByTestId(itemValue).should('not.be.empty');
it('renders correctly liquidity in trading tab', () => {
cy.getByTestId('Liquidity').click();
cy.contains('Loading').should('not.exist');
cy.contains('Something went wrong').should('not.exist');
cy.contains('Application error').should('not.exist');
cy.getByTestId('tab-liquidity').within(() => {
cy.get('[col-id="party.id"]').eq(1).should('not.be.empty');
});
});
});
}
);
it('must see market change', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketChange).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Change (24h)');
cy.getByTestId(percentageValue).should('not.be.empty');
cy.getByTestId(priceChangeValue).should('not.be.empty');
describe(
'Console - market summary - live env',
{ tags: '@live', testIsolation: true },
() => {
before(() => {
cy.visit('/');
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 volume', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketVolume).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Volume (24h)');
cy.getByTestId(itemValue).should('not.be.empty');
it('must see market price', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketPrice).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Price');
cy.getByTestId(itemValue).should('not.be.empty');
});
});
});
});
it('must see market mode', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketMode).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Trading mode');
cy.getByTestId(itemValue).should('not.be.empty');
it('must see market change', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketChange).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Change (24h)');
cy.getByTestId(percentageValue).should('not.be.empty');
cy.getByTestId(priceChangeValue).should('not.be.empty');
});
});
});
});
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');
it('must see market volume', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketVolume).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Volume (24h)');
cy.getByTestId(itemValue).should('not.be.empty');
});
});
});
});
});
describe('Console - markets table - live env', { tags: '@live' }, () => {
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);
it('must see market mode', () => {
cy.getByTestId(marketSummaryBlock).within(() => {
cy.getByTestId(marketMode).within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Trading mode');
cy.getByTestId(itemValue).should('not.be.empty');
});
});
});
});
});
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() {
cy.contains('Loading...').should('not.exist');
@@ -12,7 +12,7 @@ import {
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { updateGridData } from '@vegaprotocol/react-helpers';
import { updateGridData } from '@vegaprotocol/datagrid';
import {
NetworkParams,
useNetworkParams,
@@ -155,6 +155,7 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketOpenOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -166,6 +167,7 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketClosedOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -177,6 +179,7 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketRejectOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -187,6 +190,7 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketAllOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -195,6 +199,7 @@ const MarketBottomPanel = memo(
<TradingViews.fills.component
marketId={marketId}
onMarketClick={onMarketClick}
storeKey="marketFills"
/>
</VegaWalletContainer>
</Tab>
@@ -213,6 +218,7 @@ const MarketBottomPanel = memo(
<TradingViews.positions.component
onMarketClick={onMarketClick}
noBottomPlaceholder
storeKey="marketPositions"
/>
</VegaWalletContainer>
</Tab>
@@ -222,6 +228,7 @@ const MarketBottomPanel = memo(
pinnedAsset={pinnedAsset}
noBottomPlaceholder
hideButtons
storeKey="marketCollateral"
/>
</VegaWalletContainer>
</Tab>
@@ -234,7 +241,10 @@ const MarketBottomPanel = memo(
<Tabs storageKey="console-trade-grid-bottom">
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.positions.component onMarketClick={onMarketClick} />
<TradingViews.positions.component
onMarketClick={onMarketClick}
storeKey="marketPositions"
/>
</VegaWalletContainer>
</Tab>
<Tab id="open-orders" name={t('Open')}>
@@ -245,6 +255,7 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketOpenOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -256,6 +267,7 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketClosedOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -267,6 +279,7 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketRejectedOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -277,6 +290,7 @@ const MarketBottomPanel = memo(
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketAllOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -285,6 +299,7 @@ const MarketBottomPanel = memo(
<TradingViews.fills.component
marketId={marketId}
onMarketClick={onMarketClick}
storeKey="marketFills"
/>
</VegaWalletContainer>
</Tab>
@@ -293,6 +308,7 @@ const MarketBottomPanel = memo(
<TradingViews.collateral.component
pinnedAsset={pinnedAsset}
hideButtons
storeKey="marketCollateral"
/>
</VegaWalletContainer>
</Tab>
+2 -1
View File
@@ -264,6 +264,7 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
{
headerName: t('Market ID'),
field: 'id',
flex: 1,
},
];
return cols;
@@ -276,10 +277,10 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
columnDefs={colDefs}
getRowId={({ data }) => data.id}
defaultColDef={{
flex: 1,
resizable: true,
}}
overlayNoRowsTemplate="No data"
storeKey="closedMarkets"
/>
);
};
@@ -2,7 +2,7 @@ import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useRef } from 'react';
@@ -53,6 +53,7 @@ export const Portfolio = () => {
<PositionsContainer
onMarketClick={onMarketClick}
noBottomPlaceholder
storeKey="portfolioPositions"
/>
</VegaWalletContainer>
</Tab>
@@ -61,12 +62,16 @@ export const Portfolio = () => {
<OrderListContainer
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
storeKey="portfolioOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<FillsContainer onMarketClick={onMarketClick} />
<FillsContainer
onMarketClick={onMarketClick}
storeKey="portfolioFills"
/>
</VegaWalletContainer>
</Tab>
<Tab id="ledger-entries" name={t('Ledger entries')}>
@@ -86,7 +91,7 @@ export const Portfolio = () => {
<Tabs storageKey="console-portfolio-bottom">
<Tab id="collateral" name={t('Collateral')}>
<VegaWalletContainer>
<AccountsContainer />
<AccountsContainer storeKey="portfolioCollateral" />
</VegaWalletContainer>
</Tab>
<Tab id="deposits" name={t('Deposits')}>
@@ -13,10 +13,12 @@ export const AccountsContainer = ({
pinnedAsset,
hideButtons,
noBottomPlaceholder,
storeKey,
}: {
pinnedAsset?: PinnedAsset;
hideButtons?: boolean;
noBottomPlaceholder?: boolean;
storeKey?: string;
}) => {
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
@@ -49,6 +51,7 @@ export const AccountsContainer = ({
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
noBottomPlaceholder={noBottomPlaceholder}
storeKey={storeKey}
/>
{!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">
+11
View File
@@ -93,6 +93,11 @@ html [data-theme='light'] {
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 */
.ag-theme-balham {
--ag-background-color: theme(colors.white);
@@ -116,3 +121,9 @@ html [data-theme='light'] {
--ag-row-hover-color: theme(colors.neutral[800]);
--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);
}
+4 -1
View File
@@ -1,6 +1,6 @@
import { useRef, useMemo, memo, useCallback } from 'react';
import { t } from '@vegaprotocol/i18n';
import { useBottomPlaceholder } from '@vegaprotocol/react-helpers';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
@@ -17,6 +17,7 @@ interface AccountManagerProps {
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
noBottomPlaceholder?: boolean;
storeKey?: string;
}
export const AccountManager = ({
@@ -27,6 +28,7 @@ export const AccountManager = ({
isReadOnly,
pinnedAsset,
noBottomPlaceholder,
storeKey,
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
@@ -59,6 +61,7 @@ export const AccountManager = ({
suppressLoadingOverlay
suppressNoRowsOverlay
pinnedAsset={pinnedAsset}
storeKey={storeKey}
{...bottomPlaceholderProps}
/>
<div className="pointer-events-none absolute inset-0">
+2 -2
View File
@@ -100,6 +100,7 @@ export interface AccountTableProps extends AgGridReactProps {
onClickDeposit?: (assetId: string) => void;
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
storeKey?: string;
}
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
@@ -155,7 +156,6 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
(data) => data.asset.id !== pinnedAssetId
)}
defaultColDef={{
flex: 1,
resizable: true,
tooltipComponent: TooltipCellComponent,
sortable: true,
@@ -187,7 +187,6 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
</ButtonLink>
);
}}
maxWidth={300}
/>
<AgGridColumn
headerName={t('Used')}
@@ -362,6 +361,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
);
}
}}
flex={1}
/>
}
</AgGrid>
@@ -9,9 +9,8 @@ declare global {
}
export function addGetNetworkParameters() {
// @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command
Cypress.Commands.add('get_network_parameters', () => {
const mutation = `
const query = `
{
networkParametersConnection {
edges {
@@ -26,17 +25,17 @@ export function addGetNetworkParameters() {
method: 'POST',
url: `http://localhost:3008/graphql`,
body: {
query: mutation,
query,
},
headers: { 'content-type': 'application/json' },
})
.its('body.data.networkParametersConnection.edges')
.then(function (response) {
// @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command
const object = response.reduce(function (r, e) {
const { value, key } = e.node;
r[key] = value;
return r;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const object = response.reduce(function (obj: any, edge: any) {
const { value, key } = edge.node;
obj[key] = value;
return obj;
}, {});
return cy.wrap(object);
});
+5
View File
@@ -1,4 +1,5 @@
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/flash-cell';
@@ -10,6 +11,7 @@ export * from './lib/cells/vol-cell';
export * from './lib/cells/centered-grid-cell';
export * from './lib/cells/market-name-cell';
export * from './lib/cells/order-type-cell';
export * from './lib/cells/size';
export * from './lib/filters/date-range-filter';
export * from './lib/filters/set-filter';
@@ -18,3 +20,6 @@ export * from './lib/cell-class-rules';
export * from './lib/type-helpers';
export * from './lib/cells/grid-progress-bar';
export * from './lib/ag-grid-update';
export * from './lib/use-bottom-placeholder';
@@ -1,7 +1,8 @@
import type { MutableRefObject, RefObject } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import type { IGetRowsParams } from 'ag-grid-community';
type AnyArray = Array<any> | null; // eslint-disable-line @typescript-eslint/no-explicit-any
export const isXOrWasEmpty = (prev?: AnyArray, curr?: AnyArray) =>
Boolean(Number(!!prev?.length) ^ Number(!!curr?.length));
@@ -17,14 +18,3 @@ export const updateGridData = (
}
return !rerender;
};
export const makeGetRows =
(dataRef: MutableRefObject<AnyArray>) =>
() =>
async ({ successCallback, startRow, endRow }: IGetRowsParams) => {
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow)
: [];
const lastRow = dataRef.current ? dataRef.current.length : 0;
successCallback(rowsThisBlock, lastRow);
};
@@ -1,22 +1,30 @@
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { AgGridReact } from 'ag-grid-react';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { useColumnSizes } from './use-column-sizes';
import classNames from 'classnames';
export const AgGridThemed = ({
style,
gridRef,
storeKey,
...props
}: (AgGridReactProps | AgReactUiProps) & {
style?: React.CSSProperties;
gridRef?: React.ForwardedRef<AgGridReact>;
storeKey?: string;
}) => {
const commonColumnCallbacks = useColumnSizes({
storeKey,
props,
});
const { theme } = useThemeSwitcher();
const defaultProps = {
rowHeight: 22,
headerHeight: 22,
enableCellTextSelection: true,
};
const wrapperClasses = classNames('vega-ag-grid', {
'ag-theme-balham': theme === 'light',
'ag-theme-balham-dark': theme === 'dark',
@@ -24,7 +32,12 @@ export const AgGridThemed = ({
return (
<div className={wrapperClasses} style={style}>
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
<AgGridReact
{...defaultProps}
{...props}
{...commonColumnCallbacks}
ref={gridRef}
/>
</div>
);
};
@@ -4,6 +4,7 @@ import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
type Props = AgGridReactProps & {
style?: React.CSSProperties;
gridRef?: React.Ref<AgGridReact>;
storeKey?: string;
};
export const AgGridLazyInternal = lazy(() =>
@@ -0,0 +1,117 @@
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,
});
});
});
@@ -0,0 +1,143 @@
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 +1,4 @@
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 */
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { render, screen } from '@testing-library/react';
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { generateMarket, generateMarketData } from '../../test-helpers';
import { DealTicket } from './deal-ticket';
@@ -40,12 +40,21 @@ describe('DealTicket', () => {
});
it('should display ticket defaults', () => {
render(generateJsx());
const { container } = render(generateJsx());
// Assert defaults are used
expect(
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`)
).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(
screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input')
).toBeChecked();
@@ -54,8 +63,15 @@ describe('DealTicket', () => {
).not.toBeChecked();
expect(screen.getByTestId('order-size')).toHaveDisplayValue('0');
expect(screen.getByTestId('order-tif')).toHaveValue(
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
Schema.OrderTimeInForce.TIME_IN_FORCE_GTC
);
});
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
expect(screen.getByTestId('last-price')).toHaveTextContent(
// eslint-disable-next-line
@@ -206,7 +222,11 @@ describe('DealTicket', () => {
it('handles TIF select box dependent on order type', async () => {
render(generateJsx());
// Only FOK and IOC should be present by default (type market order)
act(() => {
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`).click();
});
// Only FOK and IOC should be present for type market order
expect(
Array.from(screen.getByTestId('order-tif').children).map(
(o) => o.textContent

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