Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aef44b228c | ||
|
|
2af3cc3e94 | ||
|
|
450b46343e | ||
|
|
e79d6c6e9e | ||
|
|
9d3d7af118 | ||
|
|
fecea28207 | ||
|
|
7ba11b8ecd | ||
|
|
1a27b19670 | ||
|
|
ded53f9f19 | ||
|
|
45cf0b2866 | ||
|
|
0814d6d991 | ||
|
|
05f450b498 | ||
|
|
0e46887479 | ||
|
|
c85ad37925 | ||
|
|
4f94151e61 | ||
|
|
b80810380f | ||
|
|
a86cb31493 | ||
|
|
d772972267 | ||
|
|
d268088e60 | ||
|
|
1208d3f2a8 | ||
|
|
b5ba4f99d9 | ||
|
|
66f25603d3 | ||
|
|
4afd469404 | ||
|
|
9e5bc9c8d1 | ||
|
|
e41aff88b1 | ||
|
|
9209074332 |
@@ -3,6 +3,3 @@
|
||||
|
||||
# Lint commit messages to ensure they follow conventional commit standards
|
||||
yarn commitlint --edit "${1}"
|
||||
|
||||
# Lint all staged files
|
||||
yarn lint-staged
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Lint all staged files
|
||||
yarn lint-staged
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Lint all staged files
|
||||
yarn nx format:check
|
||||
|
||||
# Test all projects with changes
|
||||
yarn nx affected -t test --exclude trading
|
||||
@@ -0,0 +1,68 @@
|
||||
context('Oracle page', { tags: '@smoke' }, () => {
|
||||
describe('Verify elements on page', () => {
|
||||
before('create market and navigate to oracle page', () => {
|
||||
cy.createMarket();
|
||||
cy.visit('/oracles');
|
||||
});
|
||||
it('should see oracle data', () => {
|
||||
cy.getByTestId('oracle-details').should('have.length.at.least', 2);
|
||||
cy.getByTestId('oracle-details')
|
||||
.should('exist')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'ID');
|
||||
cy.get('a').invoke('text').should('have.length', 64);
|
||||
cy.get('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/oracles/');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Type');
|
||||
cy.get('td').should('have.text', 'External data');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(2)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Signer');
|
||||
cy.getByTestId('keytype').should('have.text', 'Vega');
|
||||
cy.get('a').invoke('text').should('have.length', 64);
|
||||
cy.get('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/parties/');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Settlement for');
|
||||
cy.get('a').invoke('text').should('have.length', 64);
|
||||
cy.get('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/markets/');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(4)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Matched data');
|
||||
cy.get('td').should('have.text', '❌');
|
||||
});
|
||||
cy.get('details')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.contains('Filter').click();
|
||||
cy.get('.language-json').should('exist');
|
||||
});
|
||||
cy.get('details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.contains('JSON').click();
|
||||
cy.get('.language-json').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -38,7 +38,12 @@ const Oracles = () => {
|
||||
const dataConnection = o?.node.dataConnection;
|
||||
|
||||
return (
|
||||
<div id={id} key={id} className="mb-10">
|
||||
<div
|
||||
id={id}
|
||||
key={id}
|
||||
className="mb-10"
|
||||
data-testid="oracle-details"
|
||||
>
|
||||
<OracleDetails
|
||||
id={id}
|
||||
dataSource={o?.node}
|
||||
|
||||
@@ -54,7 +54,7 @@ export const WalletCardRow = ({
|
||||
}) => {
|
||||
const ref = React.useRef<HTMLDivElement | null>(null);
|
||||
useAnimateValue(ref, value);
|
||||
const [integers, decimalsPlaces] = useNumberParts(value, decimals);
|
||||
const [integers, decimalsPlaces, separator] = useNumberParts(value, decimals);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -75,7 +75,10 @@ export const WalletCardRow = ({
|
||||
className="font-mono flex-1 text-right"
|
||||
data-testid="associated-amount"
|
||||
>
|
||||
<span>{integers}.</span>
|
||||
<span>
|
||||
{integers}
|
||||
{separator}
|
||||
</span>
|
||||
<span>{decimalsPlaces}</span>
|
||||
</span>
|
||||
)}
|
||||
@@ -110,7 +113,10 @@ export const WalletCardAsset = ({
|
||||
border,
|
||||
subheading,
|
||||
}: WalletCardAssetProps) => {
|
||||
const [integers, decimalsPlaces] = useNumberParts(balance, decimals);
|
||||
const [integers, decimalsPlaces, separator] = useNumberParts(
|
||||
balance,
|
||||
decimals
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-nowrap mt-2 mb-4">
|
||||
@@ -132,7 +138,10 @@ export const WalletCardAsset = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2 basis-full font-mono" data-testid="currency-value">
|
||||
<span>{integers}.</span>
|
||||
<span>
|
||||
{integers}
|
||||
{separator}
|
||||
</span>
|
||||
<span className="text-neutral-400">{decimalsPlaces}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
const orderbookTab = 'Orderbook';
|
||||
const orderbookTable = 'tab-orderbook';
|
||||
const askPrice = 'price-9894185';
|
||||
const bidPrice = 'price-9889001';
|
||||
const askVolume = 'ask-vol-9894185';
|
||||
const bidVolume = 'bid-vol-9889001';
|
||||
const askCumulative = 'cumulative-vol-9894185';
|
||||
const bidCumulative = 'cumulative-vol-9889001';
|
||||
const midPrice = 'last-traded-4612690000';
|
||||
const priceResolution = 'resolution';
|
||||
const dealTicketPrice = 'order-price';
|
||||
const dealTicketSize = 'order-size';
|
||||
const resPrice = 'price-990';
|
||||
|
||||
describe('order book', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
});
|
||||
|
||||
it('show order book', () => {
|
||||
// 6003-ORDB-001
|
||||
// 6003-ORDB-002
|
||||
cy.getByTestId(orderbookTab).click();
|
||||
cy.getByTestId(orderbookTable).should('be.visible');
|
||||
cy.getByTestId(orderbookTable).should('not.be.empty');
|
||||
});
|
||||
|
||||
it('show orders prices', () => {
|
||||
// 6003-ORDB-003
|
||||
cy.getByTestId(askPrice).should('have.text', '98.94185');
|
||||
cy.getByTestId(bidPrice).should('have.text', '98.89001');
|
||||
});
|
||||
|
||||
it('show prices volumes', () => {
|
||||
// 6003-ORDB-004
|
||||
cy.getByTestId(askVolume).should('have.text', '1');
|
||||
cy.getByTestId(bidVolume).should('have.text', '1');
|
||||
});
|
||||
|
||||
it('show prices cumulative volumes', () => {
|
||||
// 6003-ORDB-005
|
||||
cy.getByTestId(askCumulative).should('have.text', '38');
|
||||
cy.getByTestId(bidCumulative).should('have.text', '7');
|
||||
});
|
||||
|
||||
it('show mid price', () => {
|
||||
// 6003-ORDB-006
|
||||
cy.getByTestId(midPrice).should('have.text', '46,126.90');
|
||||
});
|
||||
|
||||
it('sort prices descending', () => {
|
||||
// 6003-ORDB-007
|
||||
const prices: number[] = [];
|
||||
cy.getByTestId(orderbookTable).within(() => {
|
||||
cy.get('[data-testid*=price]')
|
||||
.each(($el) => {
|
||||
prices.push(Number($el.text()));
|
||||
})
|
||||
.then(() => {
|
||||
expect(prices).to.deep.equal(prices.sort((a, b) => b - a));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('copy price to deal ticket form', () => {
|
||||
// 6003-ORDB-009
|
||||
cy.getByTestId(askPrice).click();
|
||||
cy.getByTestId(dealTicketPrice).should('have.value', '98.94185');
|
||||
});
|
||||
|
||||
it('copy size to deal ticket form', () => {
|
||||
// 6003-ORDB-009
|
||||
cy.getByTestId(bidCumulative).click();
|
||||
cy.getByTestId(dealTicketSize).should('have.value', '7');
|
||||
});
|
||||
|
||||
it('copy size to deal ticket form', () => {
|
||||
// 6003-ORDB-009
|
||||
cy.getByTestId(bidVolume).click();
|
||||
cy.getByTestId(dealTicketSize).should('have.value', '1');
|
||||
});
|
||||
|
||||
it('change price resolution', () => {
|
||||
// 6003-ORDB-008
|
||||
const resolutions = [
|
||||
'0.00000',
|
||||
'0.0000',
|
||||
'0.000',
|
||||
'0.00',
|
||||
'0.0',
|
||||
'0',
|
||||
'10',
|
||||
'100',
|
||||
'1,000',
|
||||
'10,000',
|
||||
];
|
||||
cy.getByTestId(priceResolution).click();
|
||||
cy.get('[role="menu"]')
|
||||
.find('[role="menuitem"]')
|
||||
.each(($el, index) => {
|
||||
expect($el.text()).to.equal(resolutions[index]);
|
||||
});
|
||||
|
||||
cy.get('[role="menuitem"]').eq(4).click();
|
||||
cy.getByTestId(resPrice).should('have.text', '99.0');
|
||||
cy.getByTestId(askPrice).should('not.exist');
|
||||
cy.getByTestId(bidPrice).should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -1,27 +1,49 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { titlefy } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { LocalStoragePersistTabs as Tabs, Tab } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
LocalStoragePersistTabs as Tabs,
|
||||
Tab,
|
||||
TradingAnchorButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { Markets } from './markets';
|
||||
import { Proposed } from './proposed';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
import { Closed } from './closed';
|
||||
import {
|
||||
DApp,
|
||||
TOKEN_NEW_MARKET_PROPOSAL,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
|
||||
export const MarketsPage = () => {
|
||||
const { updateTitle } = usePageTitleStore((store) => ({
|
||||
updateTitle: store.updateTitle,
|
||||
}));
|
||||
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
|
||||
|
||||
useEffect(() => {
|
||||
updateTitle(titlefy(['Markets']));
|
||||
}, [updateTitle]);
|
||||
|
||||
return (
|
||||
<div className="h-full pt-0.5 pb-3 px-1.5">
|
||||
<div className="h-full my-1 border border-default rounded-sm">
|
||||
<div className="h-full my-1 border rounded-sm border-default">
|
||||
<Tabs storageKey="console-markets">
|
||||
<Tab id="open-markets" name={t('Open markets')}>
|
||||
<Markets />
|
||||
</Tab>
|
||||
<Tab id="proposed-markets" name={t('Proposed markets')}>
|
||||
<Tab
|
||||
id="proposed-markets"
|
||||
name={t('Proposed markets')}
|
||||
menu={
|
||||
<TradingAnchorButton size="extra-small" href={externalLink}>
|
||||
{t('Propose a new market')}
|
||||
</TradingAnchorButton>
|
||||
}
|
||||
>
|
||||
<Proposed />
|
||||
</Tab>
|
||||
<Tab id="closed-markets" name={t('Closed markets')}>
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
DApp,
|
||||
TOKEN_NEW_MARKET_PROPOSAL,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ProposalsList } from '@vegaprotocol/proposals';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { SuccessorMarketRenderer } from './successor-market-cell';
|
||||
|
||||
export const Proposed = () => {
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const externalLink = tokenLink(TOKEN_NEW_MARKET_PROPOSAL);
|
||||
return (
|
||||
<>
|
||||
<div className="h-[400px]">
|
||||
<ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />
|
||||
</div>
|
||||
<ExternalLink className="py-4 px-[11px] text-sm" href={externalLink}>
|
||||
{t('Propose a new market')}
|
||||
</ExternalLink>
|
||||
</>
|
||||
);
|
||||
return <ProposalsList SuccessorMarketRenderer={SuccessorMarketRenderer} />;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import compact from 'lodash/compact';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import type { AccountHistoryQuery } from './__generated__/AccountHistory';
|
||||
import { useAccountHistoryQuery } from './__generated__/AccountHistory';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
@@ -12,12 +12,13 @@ import type { AssetFieldsFragment } from '@vegaprotocol/assets';
|
||||
import { useAssetsDataProvider } from '@vegaprotocol/assets';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
Splash,
|
||||
Toggle,
|
||||
TradingButton,
|
||||
TradingDropdown,
|
||||
TradingDropdownContent,
|
||||
TradingDropdownItem,
|
||||
TradingDropdownTrigger,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import { PriceChart } from 'pennant';
|
||||
@@ -150,6 +151,7 @@ const AccountHistoryManager = ({
|
||||
)
|
||||
: null;
|
||||
}, [accounts, marketFilterCb]);
|
||||
|
||||
const resolveMarket = useCallback(
|
||||
(m: Market) => {
|
||||
setMarket(m);
|
||||
@@ -174,111 +176,114 @@ const AccountHistoryManager = ({
|
||||
}),
|
||||
[pubKey, asset, accountType, range, market?.id]
|
||||
);
|
||||
|
||||
const { data } = useAccountHistoryQuery({
|
||||
variables,
|
||||
skip: !asset || !pubKey,
|
||||
});
|
||||
|
||||
const accountTypeMenu = useMemo(() => {
|
||||
return (
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<DropdownMenuTrigger>
|
||||
{accountType
|
||||
? `${
|
||||
AccountTypeMapping[
|
||||
accountType as keyof typeof Schema.AccountType
|
||||
]
|
||||
} Account`
|
||||
: t('Select account type')}
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
{[
|
||||
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
Schema.AccountType.ACCOUNT_TYPE_BOND,
|
||||
Schema.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
].map((type) => (
|
||||
<DropdownMenuItem
|
||||
key={type}
|
||||
onClick={() => setAccountType(type as Schema.AccountType)}
|
||||
>
|
||||
{AccountTypeMapping[type as keyof typeof Schema.AccountType]}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}, [accountType]);
|
||||
const assetsMenu = useMemo(() => {
|
||||
return (
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<DropdownMenuTrigger>
|
||||
{asset ? asset.symbol : t('Select asset')}
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
{assets.map((a) => (
|
||||
<DropdownMenuItem key={a.id} onClick={() => setAssetId(a.id)}>
|
||||
{a.symbol}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}, [asset, assets, setAssetId]);
|
||||
const marketsMenu = useMemo(() => {
|
||||
return accountType === Schema.AccountType.ACCOUNT_TYPE_MARGIN &&
|
||||
markets?.length ? (
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<DropdownMenuTrigger>
|
||||
{market
|
||||
? market.tradableInstrument.instrument.code
|
||||
: t('Select market')}
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
{market && (
|
||||
<DropdownMenuItem key="0" onClick={() => setMarket(null)}>
|
||||
{t('All markets')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{markets?.map((m) => (
|
||||
<DropdownMenuItem key={m.id} onClick={() => resolveMarket(m)}>
|
||||
{m.tradableInstrument.instrument.code}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null;
|
||||
}, [markets, market, accountType, resolveMarket]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
accountType !== Schema.AccountType.ACCOUNT_TYPE_MARGIN ||
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.id !==
|
||||
asset?.id
|
||||
) {
|
||||
setMarket(null);
|
||||
}
|
||||
}, [accountType, asset?.id, market]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full flex flex-col gap-8">
|
||||
<div className="w-full flex flex-col-reverse lg:flex-row items-start lg:items-center justify-between gap-4 px-2">
|
||||
<div className="flex items-center gap-4 shrink-0">
|
||||
<>
|
||||
{accountTypeMenu}
|
||||
{assetsMenu}
|
||||
{marketsMenu}
|
||||
</>
|
||||
<div className="flex flex-col h-full gap-2">
|
||||
<div className="flex flex-wrap justify-between px-1 pt-2 gap-2">
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger>
|
||||
<TradingButton size="small">
|
||||
{accountType
|
||||
? `${
|
||||
AccountTypeMapping[
|
||||
accountType as keyof typeof Schema.AccountType
|
||||
]
|
||||
} Account`
|
||||
: t('Select account type')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent>
|
||||
{[
|
||||
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
Schema.AccountType.ACCOUNT_TYPE_BOND,
|
||||
Schema.AccountType.ACCOUNT_TYPE_MARGIN,
|
||||
].map((type) => (
|
||||
<TradingDropdownItem
|
||||
key={type}
|
||||
onClick={() => {
|
||||
setAccountType(type as Schema.AccountType);
|
||||
|
||||
// if not a margin account clear any market selection
|
||||
if (type !== Schema.AccountType.ACCOUNT_TYPE_MARGIN) {
|
||||
setMarket(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{AccountTypeMapping[type as keyof typeof Schema.AccountType]}
|
||||
</TradingDropdownItem>
|
||||
))}
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger>
|
||||
<TradingButton size="small">
|
||||
{asset ? asset.symbol : t('Select asset')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent>
|
||||
{assets.map((a) => (
|
||||
<TradingDropdownItem
|
||||
key={a.id}
|
||||
onClick={() => {
|
||||
setAssetId(a.id);
|
||||
|
||||
// if the selected asset is different to the selected market clear the market
|
||||
if (
|
||||
a.id !==
|
||||
market?.tradableInstrument.instrument.product
|
||||
.settlementAsset.id
|
||||
) {
|
||||
setMarket(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{a.symbol}
|
||||
</TradingDropdownItem>
|
||||
))}
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger>
|
||||
<TradingButton size="small">
|
||||
{market
|
||||
? market.tradableInstrument.instrument.code
|
||||
: t('Select market')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent>
|
||||
{market && (
|
||||
<TradingDropdownItem key="0" onClick={() => setMarket(null)}>
|
||||
{t('All markets')}
|
||||
</TradingDropdownItem>
|
||||
)}
|
||||
{markets?.map((m) => (
|
||||
<TradingDropdownItem
|
||||
key={m.id}
|
||||
onClick={() => resolveMarket(m)}
|
||||
>
|
||||
{m.tradableInstrument.instrument.code}
|
||||
</TradingDropdownItem>
|
||||
))}
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
</div>
|
||||
<div className="pt-1 justify-items-end">
|
||||
<div className="justify-items-end">
|
||||
<Toggle
|
||||
id="account-history-date-range"
|
||||
name="account-history-date-range"
|
||||
@@ -287,16 +292,19 @@ const AccountHistoryManager = ({
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setRange(e.target.value as keyof typeof DateRange)
|
||||
}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-5/6 px-4">
|
||||
<div className="flex-1">
|
||||
{asset && (
|
||||
<AccountHistoryChart
|
||||
data={data}
|
||||
accountType={accountType}
|
||||
asset={asset}
|
||||
/>
|
||||
<div className="h-full">
|
||||
<AccountHistoryChart
|
||||
data={data}
|
||||
accountType={accountType}
|
||||
asset={asset}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const AccountsMenu = () => {
|
||||
@@ -8,7 +8,6 @@ export const AccountsMenu = () => {
|
||||
return (
|
||||
<>
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
data-testid="open-transfer"
|
||||
onClick={() => setView({ type: ViewType.Transfer })}
|
||||
@@ -16,7 +15,6 @@ export const AccountsMenu = () => {
|
||||
{t('Transfer')}
|
||||
</TradingButton>
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const DepositsMenu = () => {
|
||||
@@ -7,7 +7,6 @@ export const DepositsMenu = () => {
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
data-testid="deposit-button"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuTrigger,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
TradingDropdown,
|
||||
TradingDropdownCheckboxItem,
|
||||
TradingDropdownContent,
|
||||
TradingDropdownItemIndicator,
|
||||
TradingDropdownTrigger,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketSelectorButton } from './market-selector-button';
|
||||
|
||||
type Assets = Array<{ id: string; symbol: string }>;
|
||||
|
||||
@@ -25,17 +24,19 @@ export const AssetDropdown = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<DropdownMenuTrigger data-testid="asset-trigger">
|
||||
<TriggerText assets={assets} checkedAssets={checkedAssets} />
|
||||
</DropdownMenuTrigger>
|
||||
<TradingDropdownTrigger data-testid="asset-trigger">
|
||||
<MarketSelectorButton>
|
||||
{triggerText({ assets, checkedAssets })}
|
||||
</MarketSelectorButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
<TradingDropdownContent>
|
||||
{assets?.map((a) => {
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
<TradingDropdownCheckboxItem
|
||||
key={a.id}
|
||||
checked={checkedAssets.includes(a.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
@@ -46,16 +47,16 @@ export const AssetDropdown = ({
|
||||
data-testid={`asset-id-${a.id}`}
|
||||
>
|
||||
{a.symbol}
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuCheckboxItem>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownCheckboxItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
const TriggerText = ({
|
||||
const triggerText = ({
|
||||
assets,
|
||||
checkedAssets,
|
||||
}: {
|
||||
@@ -72,9 +73,5 @@ const TriggerText = ({
|
||||
text = t(`${checkedAssets.length} Assets`);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="flex justify-between items-center">
|
||||
{text} <VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</span>
|
||||
);
|
||||
return text;
|
||||
};
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './market-selector';
|
||||
export * from './market-selector-item';
|
||||
export * from './market-selector-button';
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import type { ButtonHTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
export const MarketSelectorButton = forwardRef<
|
||||
HTMLButtonElement,
|
||||
ButtonHTMLAttributes<HTMLButtonElement>
|
||||
>((props, ref) => (
|
||||
<button
|
||||
{...props}
|
||||
className={classNames(
|
||||
'flex items-center justify-between px-2 border rounded gap-1',
|
||||
'border-vega-clight-600 dark:border-vega-cdark-600 bg-vega-clight-700 dark:bg-vega-cdark-700',
|
||||
'text-secondary data-[state=open]:text-vega-clight-50 dark:data-[state=open]:text-vega-cdark-50'
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
{props.children}
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</button>
|
||||
));
|
||||
MarketSelectorButton.displayName = 'MarketSelectorButton';
|
||||
@@ -31,7 +31,7 @@ export const MarketSelectorItem = ({
|
||||
<div style={style} role="row">
|
||||
<Link
|
||||
to={`/markets/${market.id}`}
|
||||
className={classNames('h-full flex items-center gap-2 px-4', {
|
||||
className={classNames('h-full flex items-center gap-2 mx-2 px-2', {
|
||||
'hover:bg-vega-clight-700 dark:hover:bg-vega-cdark-700':
|
||||
market.id !== currentMarketId,
|
||||
'bg-vega-clight-600 dark:bg-vega-cdark-600':
|
||||
@@ -94,7 +94,7 @@ const MarketData = ({
|
||||
return (
|
||||
<>
|
||||
<div className="w-2/5" role="gridcell">
|
||||
<h3 className="text-ellipsis text-sm lg:text-base whitespace-nowrap overflow-hidden">
|
||||
<h3 className="overflow-hidden text-sm text-ellipsis lg:text-base whitespace-nowrap">
|
||||
{market.tradableInstrument.instrument.code}{' '}
|
||||
{allProducts && productType && (
|
||||
<MarketProductPill productType={productType} />
|
||||
@@ -107,7 +107,7 @@ const MarketData = ({
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="w-1/5 text-xs lg:text-sm whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
className="w-1/5 overflow-hidden text-xs lg:text-sm whitespace-nowrap text-ellipsis"
|
||||
title={instrument.product.settlementAsset.symbol}
|
||||
data-testid="market-selector-price"
|
||||
role="gridcell"
|
||||
@@ -115,14 +115,14 @@ const MarketData = ({
|
||||
{price} {instrument.product.settlementAsset.symbol}
|
||||
</div>
|
||||
<div
|
||||
className="w-1/5 text-xs lg:text-sm text-right whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
className="w-1/5 overflow-hidden text-xs text-right lg:text-sm whitespace-nowrap text-ellipsis"
|
||||
title={t('24h vol')}
|
||||
data-testid="market-selector-volume"
|
||||
role="gridcell"
|
||||
>
|
||||
{volume}
|
||||
</div>
|
||||
<div className="w-1/5 flex justify-end" role="gridcell">
|
||||
<div className="flex justify-end w-1/5" role="gridcell">
|
||||
{oneDayCandles && (
|
||||
<Sparkline
|
||||
width={64}
|
||||
|
||||
@@ -262,9 +262,7 @@ describe('MarketSelector', () => {
|
||||
await userEvent.click(screen.getByTestId('sort-trigger'));
|
||||
const options = screen.getAllByTestId(/sort-item/);
|
||||
expect(options.map((o) => o.textContent?.trim())).toEqual(
|
||||
Object.entries(Sort)
|
||||
.filter(([key]) => key !== Sort.None)
|
||||
.map(([key]) => SortTypeMapping[key as SortType])
|
||||
Object.entries(Sort).map(([key]) => SortTypeMapping[key as SortType])
|
||||
);
|
||||
await userEvent.click(screen.getByTestId('sort-item-Gained'));
|
||||
expect(
|
||||
|
||||
@@ -40,7 +40,7 @@ export const MarketSelector = ({
|
||||
const [filter, setFilter] = useState<Filter>({
|
||||
searchTerm: '',
|
||||
product: Product.All,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
const allProducts = filter.product === Product.All;
|
||||
@@ -52,8 +52,8 @@ export const MarketSelector = ({
|
||||
}, [reload]);
|
||||
|
||||
return (
|
||||
<div data-testid="market-selector">
|
||||
<div className="pt-2 px-2 mb-2">
|
||||
<div data-testid="market-selector" className="md:w-[580px]">
|
||||
<div className="px-2 pt-2 mb-2">
|
||||
<ProductSelector
|
||||
product={filter.product}
|
||||
onSelect={(product) => {
|
||||
@@ -106,9 +106,6 @@ export const MarketSelector = ({
|
||||
currentSort={filter.sort}
|
||||
onSelect={(sort) => {
|
||||
setFilter((curr) => {
|
||||
if (curr.sort === sort) {
|
||||
return { ...curr, sort: Sort.None };
|
||||
}
|
||||
return {
|
||||
...curr,
|
||||
sort,
|
||||
@@ -294,9 +291,9 @@ const List = ({
|
||||
|
||||
const Skeleton = () => {
|
||||
return (
|
||||
<div className="mb-2 px-2">
|
||||
<div className="bg-vega-light-100 dark:bg-vega-dark-100 rounded-lg p-4">
|
||||
<div className="w-full h-3 bg-vega-light-200 dark:bg-vega-dark-200 mb-2" />
|
||||
<div className="px-2 mb-2">
|
||||
<div className="p-4 rounded-lg bg-vega-light-100 dark:bg-vega-dark-100">
|
||||
<div className="w-full h-3 mb-2 bg-vega-light-200 dark:bg-vega-dark-200" />
|
||||
<div className="w-2/3 h-3 bg-vega-light-200 dark:bg-vega-dark-200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,11 +33,14 @@ export const ProductSelector = ({
|
||||
return (
|
||||
<div className="flex mb-2">
|
||||
{Object.keys(Product).map((t) => {
|
||||
const classes = classNames('px-3 py-1.5 rounded', {
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500 text-default':
|
||||
t === product,
|
||||
'text-secondary': t !== product,
|
||||
});
|
||||
const classes = classNames(
|
||||
'text-sm px-3 py-1.5 rounded hover:text-vega-clight-50 dark:hover:text-vega-cdark-50',
|
||||
{
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500 text-default':
|
||||
t === product,
|
||||
'text-secondary': t !== product,
|
||||
}
|
||||
);
|
||||
return (
|
||||
<button
|
||||
key={t}
|
||||
@@ -53,7 +56,7 @@ export const ProductSelector = ({
|
||||
})}
|
||||
<Link
|
||||
to={Routes.MARKETS}
|
||||
className="flex items-center gap-2 ml-auto"
|
||||
className="flex items-center ml-auto text-sm gap-2"
|
||||
title={t('See all markets')}
|
||||
>
|
||||
<span className="underline underline-offset-4">{t('Browse')}</span>
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
TradingDropdown,
|
||||
TradingDropdownContent,
|
||||
TradingDropdownItemIndicator,
|
||||
TradingDropdownRadioGroup,
|
||||
TradingDropdownRadioItem,
|
||||
TradingDropdownTrigger,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketSelectorButton } from './market-selector-button';
|
||||
|
||||
export const Sort = {
|
||||
None: 'None',
|
||||
Gained: 'Gained',
|
||||
Lost: 'Lost',
|
||||
New: 'New',
|
||||
@@ -23,17 +22,15 @@ export type SortType = keyof typeof Sort;
|
||||
export const SortTypeMapping: {
|
||||
[key in SortType]: string;
|
||||
} = {
|
||||
[Sort.None]: 'None',
|
||||
[Sort.TopTraded]: 'Top traded',
|
||||
[Sort.Gained]: 'Top gaining',
|
||||
[Sort.Lost]: 'Top losing',
|
||||
[Sort.New]: 'New markets',
|
||||
[Sort.TopTraded]: 'Top traded',
|
||||
};
|
||||
|
||||
const SortIconMapping: {
|
||||
[key in SortType]: VegaIconNames;
|
||||
} = {
|
||||
[Sort.None]: null as unknown as VegaIconNames, // not shown in list
|
||||
[Sort.Gained]: VegaIconNames.TREND_UP,
|
||||
[Sort.Lost]: VegaIconNames.TREND_DOWN,
|
||||
[Sort.New]: VegaIconNames.STAR,
|
||||
@@ -48,43 +45,38 @@ export const SortDropdown = ({
|
||||
onSelect: (sort: SortType) => void;
|
||||
}) => {
|
||||
return (
|
||||
<DropdownMenu
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<DropdownMenuTrigger data-testid="sort-trigger">
|
||||
<span className="flex justify-between items-center">
|
||||
{currentSort === SortTypeMapping.None
|
||||
? t('Sort')
|
||||
: SortTypeMapping[currentSort]}{' '}
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</span>
|
||||
</DropdownMenuTrigger>
|
||||
<TradingDropdownTrigger data-testid="sort-trigger">
|
||||
<MarketSelectorButton>
|
||||
{SortTypeMapping[currentSort]}
|
||||
</MarketSelectorButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuRadioGroup
|
||||
<TradingDropdownContent>
|
||||
<TradingDropdownRadioGroup
|
||||
value={currentSort}
|
||||
onValueChange={(value) => onSelect(value as SortType)}
|
||||
>
|
||||
{Object.keys(Sort)
|
||||
.filter((s) => s !== Sort.None)
|
||||
.map((key) => {
|
||||
return (
|
||||
<DropdownMenuRadioItem
|
||||
inset
|
||||
key={key}
|
||||
value={key}
|
||||
data-testid={`sort-item-${key}`}
|
||||
>
|
||||
<span className="flex gap-2">
|
||||
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
|
||||
{SortTypeMapping[key as SortType]}
|
||||
</span>
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
{Object.keys(Sort).map((key) => {
|
||||
return (
|
||||
<TradingDropdownRadioItem
|
||||
inset
|
||||
key={key}
|
||||
value={key}
|
||||
data-testid={`sort-item-${key}`}
|
||||
>
|
||||
<span className="flex gap-2">
|
||||
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
|
||||
{SortTypeMapping[key as SortType]}
|
||||
</span>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownRadioItem>
|
||||
);
|
||||
})}
|
||||
</TradingDropdownRadioGroup>
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,10 @@ import { useMarketList } from '@vegaprotocol/markets';
|
||||
import type { Filter } from '../../components/market-selector';
|
||||
import { subDays } from 'date-fns';
|
||||
|
||||
jest.mock('@vegaprotocol/markets');
|
||||
jest.mock('@vegaprotocol/markets', () => ({
|
||||
...jest.requireActual('@vegaprotocol/markets'),
|
||||
useMarketList: jest.fn(),
|
||||
}));
|
||||
const mockUseMarketList = useMarketList as jest.Mock;
|
||||
|
||||
describe('useMarketSelectorList', () => {
|
||||
@@ -20,7 +23,7 @@ describe('useMarketSelectorList', () => {
|
||||
const defaultArgs: Filter = {
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
};
|
||||
return renderHook((args) => useMarketSelectorList(args), {
|
||||
@@ -109,21 +112,21 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Spot as 'Future',
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[1]]);
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Perpetual as 'Future',
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[2]]);
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.All,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual(markets);
|
||||
@@ -189,7 +192,7 @@ describe('useMarketSelectorList', () => {
|
||||
const { result, rerender } = setup({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: ['asset-0'],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[0], markets[1]]);
|
||||
@@ -197,7 +200,7 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: ['asset-0', 'asset-1'],
|
||||
});
|
||||
|
||||
@@ -210,7 +213,7 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: ['asset-0', 'asset-1', 'asset-2'],
|
||||
});
|
||||
|
||||
@@ -220,7 +223,7 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: ['asset-invalid'],
|
||||
});
|
||||
|
||||
@@ -275,28 +278,28 @@ describe('useMarketSelectorList', () => {
|
||||
const { result, rerender } = setup({
|
||||
searchTerm: 'abc',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[0]]);
|
||||
rerender({
|
||||
searchTerm: 'def',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[1], markets[2]]);
|
||||
rerender({
|
||||
searchTerm: 'defg',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[2]]);
|
||||
rerender({
|
||||
searchTerm: 'zzz',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([]);
|
||||
@@ -305,14 +308,14 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: 'aaa',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[0]]);
|
||||
rerender({
|
||||
searchTerm: 'ggg',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([
|
||||
@@ -322,11 +325,15 @@ describe('useMarketSelectorList', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('sorts by state and volume by default', () => {
|
||||
it('sorts by top traded by default', () => {
|
||||
const markets = [
|
||||
createMarketFragment({
|
||||
id: 'market-0',
|
||||
state: MarketState.STATE_PENDING,
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
@@ -337,30 +344,42 @@ describe('useMarketSelectorList', () => {
|
||||
createMarketFragment({
|
||||
id: 'market-1',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
volume: '200',
|
||||
volume: '100',
|
||||
},
|
||||
],
|
||||
}),
|
||||
createMarketFragment({
|
||||
id: 'market-2',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
volume: '100',
|
||||
volume: '300',
|
||||
},
|
||||
],
|
||||
}),
|
||||
createMarketFragment({
|
||||
state: MarketState.STATE_PENDING,
|
||||
id: 'market-3',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
volume: '100',
|
||||
volume: '400',
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -375,14 +394,15 @@ describe('useMarketSelectorList', () => {
|
||||
const { result } = setup({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
|
||||
expect(result.current.markets).toEqual([
|
||||
markets[1],
|
||||
markets[3],
|
||||
markets[2],
|
||||
markets[0],
|
||||
markets[3],
|
||||
markets[1],
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import {
|
||||
calcCandleVolume,
|
||||
calcTradedFactor,
|
||||
useMarketList,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
|
||||
import { priceChangePercentage } from '@vegaprotocol/utils';
|
||||
import type { Filter } from '../../components/market-selector/market-selector';
|
||||
import { Sort } from './sort-dropdown';
|
||||
@@ -60,22 +56,6 @@ export const useMarketSelectorList = ({
|
||||
return false;
|
||||
});
|
||||
|
||||
if (sort === Sort.None) {
|
||||
// Sort by market state primarily and AtoZ secondarily
|
||||
return orderBy(
|
||||
markets,
|
||||
[
|
||||
(m) => MARKET_TEMPLATE.indexOf(m.state),
|
||||
(m) => {
|
||||
if (!m.candles?.length) return 0;
|
||||
const vol = calcCandleVolume(m.candles);
|
||||
return Number(vol || 0);
|
||||
},
|
||||
],
|
||||
['asc', 'desc']
|
||||
);
|
||||
}
|
||||
|
||||
if (sort === Sort.Gained || sort === Sort.Lost) {
|
||||
const dir = sort === Sort.Gained ? 'desc' : 'asc';
|
||||
return orderBy(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { usePositionsStore } from '../positions-container';
|
||||
|
||||
export const PositionsMenu = () => {
|
||||
@@ -7,7 +7,6 @@ export const PositionsMenu = () => {
|
||||
const toggle = usePositionsStore((store) => store.toggleClosedMarkets);
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
data-testid="open-transfer"
|
||||
onClick={toggle}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './vega-wallet-container';
|
||||
@@ -1,27 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { VegaWalletContainer } from './vega-wallet-container';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
|
||||
const generateJsx = (context: PartialDeep<VegaWalletContextShape>) => {
|
||||
return (
|
||||
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
|
||||
<VegaWalletContainer>
|
||||
<div data-testid="child" />
|
||||
</VegaWalletContainer>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('VegaWalletContainer', () => {
|
||||
it('doesnt render children if not connected', () => {
|
||||
render(generateJsx({ pubKey: null }));
|
||||
expect(screen.queryByTestId('child')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders children if connected', () => {
|
||||
render(generateJsx({ pubKey: '0x123' }));
|
||||
expect(screen.getByTestId('child')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
|
||||
|
||||
interface VegaWalletContainerProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
|
||||
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
|
||||
openVegaWalletDialog: store.openVegaWalletDialog,
|
||||
}));
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<Splash>
|
||||
<div className="text-center">
|
||||
<p className="mb-4" data-testid="connect-vega-wallet-text">
|
||||
{t('Connect your Vega wallet')}
|
||||
</p>
|
||||
<Button
|
||||
onClick={openVegaWalletDialog}
|
||||
data-testid="vega-wallet-connect"
|
||||
>
|
||||
{t('Connect')}
|
||||
</Button>
|
||||
</div>
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
@@ -16,29 +16,32 @@ export const TelemetryApproval = ({
|
||||
setTelemetryValue,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className="flex flex-col py-3">
|
||||
<div className="flex flex-col">
|
||||
<div className="mr-4" role="form">
|
||||
<div className="mt-2">
|
||||
<p className="mb-4">
|
||||
{t(
|
||||
'Help us identify bugs and improve Vega Governance by sharing anonymous usage data.'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center mt-2">
|
||||
<VegaIcon name={VegaIconNames.EYE_OFF} size={24} />
|
||||
<div className="flex flex-col gap-1 ml-6">
|
||||
<div className="font-semibold">{t('Anonymous')}</div>
|
||||
<div>{t('Your identity is always anonymous on Vega')}</div>
|
||||
</p>
|
||||
<div className="flex items-start mb-2 gap-3">
|
||||
<VegaIcon name={VegaIconNames.EYE_OFF} size={18} />
|
||||
<div className="flex flex-col gap-1">
|
||||
<h6 className="font-semibold">{t('Anonymous')}</h6>
|
||||
<p className="text-muted">
|
||||
{t('Your identity is always anonymous on Vega')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center mt-2">
|
||||
<VegaIcon name={VegaIconNames.COG} size={24} />
|
||||
<div className="flex flex-col gap-1 ml-6">
|
||||
<div className="font-semibold">{t('Optional')}</div>
|
||||
<div>{t('You can opt out any time via settings')}</div>
|
||||
<div className="flex items-start mb-4 gap-3">
|
||||
<VegaIcon name={VegaIconNames.COG} size={18} />
|
||||
<div className="flex flex-col gap-1">
|
||||
<h6 className="font-semibold">{t('Optional')}</h6>
|
||||
<p className="text-muted">
|
||||
{t('You can opt out any time via settings')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-around items-center mt-10 gap-4 w-full px-4">
|
||||
<div className="flex flex-col items-center justify-around gap-2">
|
||||
<TradingButton
|
||||
onClick={() => setTelemetryValue('false')}
|
||||
size="small"
|
||||
|
||||
@@ -92,7 +92,7 @@ export const WelcomeDialog = () => {
|
||||
intent: Intent.Primary,
|
||||
content: (
|
||||
<>
|
||||
<h3 className="text-sm uppercase mb-1">
|
||||
<h3 className="mb-1 text-sm uppercase">
|
||||
{t('Improve vega console')}
|
||||
</h3>
|
||||
<TelemetryApproval
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const WithdrawalsMenu = () => {
|
||||
@@ -7,7 +7,6 @@ export const WithdrawalsMenu = () => {
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Withdraw })}
|
||||
data-testid="withdraw-dialog-button"
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ETHERSCAN_ADDRESS, useEtherscanLink } from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
ActionsDropdown,
|
||||
DropdownMenuCopyItem,
|
||||
DropdownMenuItem,
|
||||
TradingDropdownCopyItem,
|
||||
TradingDropdownItem,
|
||||
Link,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
@@ -30,49 +30,49 @@ export const AccountsActionsDropdown = ({
|
||||
|
||||
return (
|
||||
<ActionsDropdown>
|
||||
<DropdownMenuItem
|
||||
<TradingDropdownItem
|
||||
key={'deposit'}
|
||||
data-testid="deposit"
|
||||
onClick={onClickDeposit}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.DEPOSIT} size={16} />
|
||||
{t('Deposit')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
</TradingDropdownItem>
|
||||
<TradingDropdownItem
|
||||
key={'withdraw'}
|
||||
data-testid="withdraw"
|
||||
onClick={onClickWithdraw}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.WITHDRAW} size={16} />
|
||||
{t('Withdraw')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
</TradingDropdownItem>
|
||||
<TradingDropdownItem
|
||||
key={'transfer'}
|
||||
data-testid="transfer"
|
||||
onClick={onClickTransfer}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.TRANSFER} size={16} />
|
||||
{t('Transfer')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
</TradingDropdownItem>
|
||||
<TradingDropdownItem
|
||||
key={'breakdown'}
|
||||
data-testid="breakdown"
|
||||
onClick={onClickBreakdown}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.BREAKDOWN} size={16} />
|
||||
{t('View usage breakdown')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
</TradingDropdownItem>
|
||||
<TradingDropdownItem
|
||||
onClick={(e) => {
|
||||
openAssetDialog(assetId, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View asset details')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuCopyItem value={assetId} text={t('Copy asset ID')} />
|
||||
</TradingDropdownItem>
|
||||
<TradingDropdownCopyItem value={assetId} text={t('Copy asset ID')} />
|
||||
{assetContractAddress && (
|
||||
<DropdownMenuItem>
|
||||
<TradingDropdownItem>
|
||||
<Link
|
||||
href={etherscanLink(
|
||||
ETHERSCAN_ADDRESS.replace(':hash', assetContractAddress)
|
||||
@@ -84,7 +84,7 @@ export const AccountsActionsDropdown = ({
|
||||
{t('View on Etherscan')}
|
||||
</span>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</TradingDropdownItem>
|
||||
)}
|
||||
</ActionsDropdown>
|
||||
);
|
||||
|
||||
@@ -11,9 +11,13 @@ import type {
|
||||
VegaValueFormatterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import { COL_DEFS } from '@vegaprotocol/datagrid';
|
||||
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Intent,
|
||||
TradingButton,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
TooltipCellComponent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
IGetRowsParams,
|
||||
@@ -186,7 +190,7 @@ export const AccountTable = ({
|
||||
) : (
|
||||
<>
|
||||
<span className="underline">{valueFormatted}</span>
|
||||
<span className="ml-2 inline-block w-14 text-muted">
|
||||
<span className="inline-block ml-2 w-14 text-muted">
|
||||
{t('0.00%')}
|
||||
</span>
|
||||
</>
|
||||
@@ -248,26 +252,26 @@ export const AccountTable = ({
|
||||
colId: 'accounts-actions',
|
||||
field: 'asset.id',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showDepositButton ? 130 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showDepositButton ? 130 : COL_DEFS.actions.maxWidth,
|
||||
minWidth: showDepositButton ? 105 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showDepositButton ? 105 : COL_DEFS.actions.maxWidth,
|
||||
cellRenderer: ({
|
||||
value: assetId,
|
||||
node,
|
||||
}: VegaICellRendererParams<AccountFields, 'asset.id'>) => {
|
||||
if (!assetId) return null;
|
||||
if (node.rowPinned && node.data?.total === '0') {
|
||||
if (node.rowPinned && node.data?.balance === '0') {
|
||||
return (
|
||||
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="primary"
|
||||
<TradingButton
|
||||
size="extra-small"
|
||||
intent={Intent.Primary}
|
||||
data-testid="deposit"
|
||||
onClick={() => {
|
||||
onClickDeposit && onClickDeposit(assetId);
|
||||
}}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
||||
</Button>
|
||||
</TradingButton>
|
||||
</CenteredGridCellWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Button,
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
TradingInputError,
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
TradingSelect,
|
||||
Tooltip,
|
||||
TradingCheckbox,
|
||||
TradingButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { Transfer } from '@vegaprotocol/wallet';
|
||||
import { normalizeTransfer } from '@vegaprotocol/wallet';
|
||||
@@ -276,9 +276,9 @@ export const TransferForm = ({
|
||||
decimals={asset?.decimals}
|
||||
/>
|
||||
)}
|
||||
<Button type="submit" variant="primary" fill={true}>
|
||||
<TradingButton type="submit" fill={true}>
|
||||
{t('Confirm transfer')}
|
||||
</Button>
|
||||
</TradingButton>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -309,8 +309,8 @@ export const TransferFee = ({
|
||||
const totalValue = new BigNumber(transferAmount).plus(fee).toString();
|
||||
|
||||
return (
|
||||
<div className="mb-4 flex flex-col gap-2 text-xs">
|
||||
<div className="flex justify-between gap-1 items-center flex-wrap">
|
||||
<div className="flex flex-col mb-4 text-xs gap-2">
|
||||
<div className="flex flex-wrap items-center justify-between gap-1">
|
||||
<Tooltip
|
||||
description={t(
|
||||
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to %s`,
|
||||
@@ -324,7 +324,7 @@ export const TransferFee = ({
|
||||
{formatNumber(fee, decimals)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between gap-1 items-center flex-wrap">
|
||||
<div className="flex flex-wrap items-center justify-between gap-1">
|
||||
<Tooltip
|
||||
description={t(
|
||||
`The total amount to be transferred (without the fee)`
|
||||
@@ -337,7 +337,7 @@ export const TransferFee = ({
|
||||
{formatNumber(amount, decimals)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between gap-1 items-center flex-wrap">
|
||||
<div className="flex flex-wrap items-center justify-between gap-1">
|
||||
<Tooltip
|
||||
description={t(
|
||||
`The total amount taken from your account. The amount to be transferred plus the fee.`
|
||||
@@ -384,7 +384,7 @@ export const AddressField = ({
|
||||
setIsInput((curr) => !curr);
|
||||
onChange();
|
||||
}}
|
||||
className="ml-auto text-sm absolute top-0 right-0 underline"
|
||||
className="absolute top-0 right-0 ml-auto text-sm underline"
|
||||
>
|
||||
{isInput ? t('Select from wallet') : t('Enter manually')}
|
||||
</button>
|
||||
|
||||
@@ -2,9 +2,10 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
Icon,
|
||||
Splash,
|
||||
SyntaxHighlighter,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { create } from 'zustand';
|
||||
import { AssetDetailsTable } from './asset-details-table';
|
||||
@@ -82,7 +83,7 @@ export const AssetDetailsDialog = ({
|
||||
return (
|
||||
<Dialog
|
||||
title={title}
|
||||
icon={<Icon name="info-sign"></Icon>}
|
||||
icon={<VegaIcon name={VegaIconNames.INFO} />}
|
||||
open={open}
|
||||
onChange={(isOpen) => onChange(isOpen)}
|
||||
onCloseAutoFocus={(e) => {
|
||||
@@ -97,7 +98,7 @@ export const AssetDetailsDialog = ({
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
<p className="text-xs my-4">
|
||||
<p className="my-4 text-xs">
|
||||
{t(
|
||||
'There is 1 unit of the settlement asset (%s) to every 1 quote unit.',
|
||||
[assetSymbol]
|
||||
|
||||
@@ -3,11 +3,8 @@ import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import type { KeyValueTableRowProps } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
CopyWithTooltip,
|
||||
Icon,
|
||||
truncateMiddle,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { CopyWithTooltip, truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
@@ -118,7 +115,7 @@ export const rows: Rows = [
|
||||
</EtherscanLink>{' '}
|
||||
<CopyWithTooltip text={asset.source.contractAddress}>
|
||||
<button title={t('Copy address to clipboard')}>
|
||||
<Icon size={3} name="duplicate" />
|
||||
<VegaIcon size={14} name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyWithTooltip>
|
||||
</>
|
||||
|
||||
@@ -3,15 +3,31 @@ import userEvent from '@testing-library/user-event';
|
||||
import { CandlesMenu } from './candles-menu';
|
||||
|
||||
describe('CandlesMenu', () => {
|
||||
it('should render with volume study showing by default', async () => {
|
||||
it('should render with the correct default studies', async () => {
|
||||
render(<CandlesMenu />);
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByText('Studies', {
|
||||
selector: '[type="button"]',
|
||||
screen.getByRole('button', {
|
||||
name: 'Studies',
|
||||
})
|
||||
);
|
||||
expect(await screen.findByRole('menu')).toBeInTheDocument();
|
||||
expect(screen.getByText('Volume')).toHaveAttribute('data-state', 'checked');
|
||||
expect(screen.getByText('MACD')).toHaveAttribute('data-state', 'checked');
|
||||
});
|
||||
|
||||
it('should render with the correct default overlays', async () => {
|
||||
render(<CandlesMenu />);
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'Overlays',
|
||||
})
|
||||
);
|
||||
expect(await screen.findByRole('menu')).toBeInTheDocument();
|
||||
expect(screen.getByText('Moving average')).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,13 +10,14 @@ import {
|
||||
studyLabels,
|
||||
} from 'pennant';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
TradingButton,
|
||||
TradingDropdown,
|
||||
TradingDropdownCheckboxItem,
|
||||
TradingDropdownContent,
|
||||
TradingDropdownItemIndicator,
|
||||
TradingDropdownRadioGroup,
|
||||
TradingDropdownRadioItem,
|
||||
TradingDropdownTrigger,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconName } from '@blueprintjs/icons';
|
||||
@@ -44,69 +45,76 @@ export const CandlesMenu = () => {
|
||||
} = useCandlesChartSettings();
|
||||
const triggerClasses = 'text-xs';
|
||||
const contentAlign = 'end';
|
||||
const triggerButtonProps = { size: 'extra-small' } as const;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<DropdownMenuTrigger className={triggerClasses}>
|
||||
{t(`Interval: ${intervalLabels[interval]}`)}
|
||||
</DropdownMenuTrigger>
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t(`Interval: ${intervalLabels[interval]}`)}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent align={contentAlign}>
|
||||
<DropdownMenuRadioGroup
|
||||
<TradingDropdownContent align={contentAlign}>
|
||||
<TradingDropdownRadioGroup
|
||||
value={interval}
|
||||
onValueChange={(value) => {
|
||||
setInterval(value as Interval);
|
||||
}}
|
||||
>
|
||||
{Object.values(Interval).map((timeInterval) => (
|
||||
<DropdownMenuRadioItem
|
||||
<TradingDropdownRadioItem
|
||||
key={timeInterval}
|
||||
inset
|
||||
value={timeInterval}
|
||||
>
|
||||
{intervalLabels[timeInterval]}
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu
|
||||
</TradingDropdownRadioGroup>
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<DropdownMenuTrigger className={triggerClasses}>
|
||||
<Icon name={chartTypeIcon.get(chartType) as IconName} />
|
||||
</DropdownMenuTrigger>
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
<Icon name={chartTypeIcon.get(chartType) as IconName} />
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent align={contentAlign}>
|
||||
<DropdownMenuRadioGroup
|
||||
<TradingDropdownContent align={contentAlign}>
|
||||
<TradingDropdownRadioGroup
|
||||
value={chartType}
|
||||
onValueChange={(value) => {
|
||||
setType(value as ChartType);
|
||||
}}
|
||||
>
|
||||
{Object.values(ChartType).map((type) => (
|
||||
<DropdownMenuRadioItem key={type} inset value={type}>
|
||||
<TradingDropdownRadioItem key={type} inset value={type}>
|
||||
{chartTypeLabels[type]}
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu
|
||||
</TradingDropdownRadioGroup>
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<DropdownMenuTrigger className={triggerClasses}>
|
||||
{t('Overlays')}
|
||||
</DropdownMenuTrigger>
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Overlays')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent align={contentAlign}>
|
||||
<TradingDropdownContent align={contentAlign}>
|
||||
{Object.values(Overlay).map((overlay) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
<TradingDropdownCheckboxItem
|
||||
key={overlay}
|
||||
checked={overlays.includes(overlay)}
|
||||
onCheckedChange={() => {
|
||||
@@ -121,21 +129,23 @@ export const CandlesMenu = () => {
|
||||
}}
|
||||
>
|
||||
{overlayLabels[overlay]}
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuCheckboxItem>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<DropdownMenuTrigger className={triggerClasses}>
|
||||
{t('Studies')}
|
||||
</DropdownMenuTrigger>
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Studies')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent align={contentAlign}>
|
||||
<TradingDropdownContent align={contentAlign}>
|
||||
{Object.values(Study).map((study) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
<TradingDropdownCheckboxItem
|
||||
key={study}
|
||||
checked={studies.includes(study)}
|
||||
onCheckedChange={() => {
|
||||
@@ -150,11 +160,11 @@ export const CandlesMenu = () => {
|
||||
}}
|
||||
>
|
||||
{studyLabels[study]}
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuCheckboxItem>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,8 +15,8 @@ interface StoredSettings {
|
||||
const DEFAULT_CHART_SETTINGS = {
|
||||
interval: Interval.I15M,
|
||||
type: ChartType.CANDLE,
|
||||
overlays: [],
|
||||
studies: [Study.VOLUME],
|
||||
overlays: [Overlay.MOVING_AVERAGE],
|
||||
studies: [Study.MACD, Study.VOLUME],
|
||||
};
|
||||
|
||||
export const useCandlesChartSettingsStore = create<
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
import * as positionsTools from '@vegaprotocol/positions';
|
||||
import { OrdersDocument } from '@vegaprotocol/orders';
|
||||
import { formatForInput } from '@vegaprotocol/utils';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
|
||||
jest.mock('zustand');
|
||||
jest.mock('./deal-ticket-fee-details', () => ({
|
||||
@@ -36,12 +38,19 @@ const market = generateMarket();
|
||||
const marketData = generateMarketData();
|
||||
const submit = jest.fn();
|
||||
|
||||
function generateJsx(mocks: MockedResponse[] = []) {
|
||||
function generateJsx(
|
||||
mocks: MockedResponse[] = [],
|
||||
marketOverrides: PartialDeep<Market> = {}
|
||||
) {
|
||||
const joinedMarket: Market = {
|
||||
...market,
|
||||
...marketOverrides,
|
||||
} as Market;
|
||||
return (
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<VegaWalletContext.Provider value={{ pubKey, isReadOnly: false } as any}>
|
||||
<DealTicket
|
||||
market={market}
|
||||
market={joinedMarket}
|
||||
marketData={marketData}
|
||||
marketPrice={marketPrice}
|
||||
submit={submit}
|
||||
@@ -367,7 +376,6 @@ describe('DealTicket', () => {
|
||||
expect(screen.getByTestId('iceberg')).toBeDisabled();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it('handles TIF select box dependent on order type', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
@@ -533,6 +541,25 @@ describe('DealTicket', () => {
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates size when positionDecimalPlaces is negative', async () => {
|
||||
render(generateJsx([], { positionDecimalPlaces: -4 }));
|
||||
const sizeErrorMessage = 'deal-ticket-error-message-size';
|
||||
const sizeInput = 'order-size';
|
||||
await userEvent.click(screen.getByTestId('place-order'));
|
||||
// default value should be invalid
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toHaveTextContent(
|
||||
'Size cannot be lower than 10000'
|
||||
);
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10001');
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toHaveTextContent(
|
||||
'Size must be a multiple of 10000 for this market'
|
||||
);
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10000');
|
||||
expect(screen.queryByTestId(sizeErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates iceberg field', async () => {
|
||||
const peakSizeErrorMessage = 'deal-ticket-peak-error-message';
|
||||
const minimumSizeErrorMessage = 'deal-ticket-minimum-error-message';
|
||||
|
||||
@@ -305,9 +305,7 @@ describe('Deposit form', () => {
|
||||
target: { value: '8' },
|
||||
});
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByText('Deposit', { selector: '[type="submit"]' })
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Deposit' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.submitDeposit).toHaveBeenCalledWith({
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
Button,
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
TradingInputError,
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
ButtonLink,
|
||||
TradingSelect,
|
||||
truncateMiddle,
|
||||
TradingButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
@@ -186,14 +186,14 @@ export const DepositForm = ({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
<TradingButton
|
||||
onClick={openDialog}
|
||||
variant="primary"
|
||||
intent={Intent.Primary}
|
||||
type="button"
|
||||
data-testid="connect-eth-wallet-btn"
|
||||
>
|
||||
{t('Connect')}
|
||||
</Button>
|
||||
</TradingButton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@@ -435,15 +435,14 @@ const FormButton = ({ approved, selectedAsset }: FormButtonProps) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
<TradingButton
|
||||
type="submit"
|
||||
data-testid="deposit-submit"
|
||||
variant={isActive ? 'primary' : 'default'}
|
||||
fill
|
||||
disabled={invalidChain}
|
||||
disabled={!isActive || invalidChain}
|
||||
>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
</TradingButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -455,7 +454,7 @@ const UseButton = (props: UseButtonProps) => {
|
||||
<button
|
||||
{...props}
|
||||
type="button"
|
||||
className="ml-auto text-sm absolute top-0 right-0 underline"
|
||||
className="absolute top-0 right-0 ml-auto text-sm underline"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -513,7 +512,7 @@ export const AddressField = ({
|
||||
setIsInput((curr) => !curr);
|
||||
onChange();
|
||||
}}
|
||||
className="ml-auto text-sm absolute top-0 right-0 underline"
|
||||
className="absolute top-0 right-0 ml-auto text-sm underline"
|
||||
data-testid="enter-pubkey-manually"
|
||||
>
|
||||
{isInput ? t('Select from wallet') : t('Enter manually')}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { Trade } from './fills-data-provider';
|
||||
|
||||
import { FillsTable, getFeesBreakdown } from './fills-table';
|
||||
import { generateFill } from './test-helpers';
|
||||
|
||||
@@ -215,6 +214,27 @@ describe('FillsTable', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('negative positionDecimalPoints should be properly rendered in size column', async () => {
|
||||
const partyId = 'party-id';
|
||||
const negativeDecimalPositionFill = generateFill({
|
||||
...defaultFill,
|
||||
market: {
|
||||
...defaultFill.market,
|
||||
positionDecimalPlaces: -4,
|
||||
},
|
||||
});
|
||||
await act(async () => {
|
||||
render(
|
||||
<FillsTable partyId={partyId} rowData={[negativeDecimalPositionFill]} />
|
||||
);
|
||||
});
|
||||
|
||||
const sizeCell = screen
|
||||
.getAllByRole('gridcell')
|
||||
.find((c) => c.getAttribute('col-id') === 'size');
|
||||
expect(sizeCell).toHaveTextContent('3,000,000,000');
|
||||
});
|
||||
|
||||
describe('getFeesBreakdown', () => {
|
||||
it('should return correct fees breakdown for a taker', () => {
|
||||
const fees = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
Button,
|
||||
TradingButton,
|
||||
Loader,
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
@@ -157,15 +157,14 @@ export const LedgerExportForm = ({ partyId, vegaUrl, assets }: Props) => {
|
||||
<Loader size="small" />
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
<TradingButton
|
||||
fill
|
||||
disabled={disabled}
|
||||
type="submit"
|
||||
data-testid="ledger-download-button"
|
||||
>
|
||||
{t('Download')}
|
||||
</Button>
|
||||
</TradingButton>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
TradingDropdownContent,
|
||||
TradingDropdownItem,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { formatNumberFixed } from '@vegaprotocol/utils';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
|
||||
export const OrderbookControls = ({
|
||||
lastTradedPrice,
|
||||
@@ -15,27 +15,14 @@ export const OrderbookControls = ({
|
||||
decimalPlaces,
|
||||
setResolution,
|
||||
}: {
|
||||
lastTradedPrice: string | undefined;
|
||||
lastTradedPrice: string;
|
||||
resolution: number;
|
||||
decimalPlaces: number;
|
||||
setResolution: (resolution: number) => void;
|
||||
}) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
|
||||
const resolutions = new Array(
|
||||
Math.max(lastTradedPrice?.toString().length ?? 0, decimalPlaces + 1)
|
||||
)
|
||||
.fill(null)
|
||||
.map((v, i) => Math.pow(10, i));
|
||||
|
||||
const formatResolution = (r: number) => {
|
||||
return formatNumberFixed(
|
||||
Math.log10(r) - decimalPlaces > 0
|
||||
? Math.pow(10, Math.log10(r) - decimalPlaces)
|
||||
: 0,
|
||||
decimalPlaces - Math.log10(r)
|
||||
);
|
||||
};
|
||||
const resolutions = createResolutions(lastTradedPrice, decimalPlaces);
|
||||
|
||||
const increaseResolution = () => {
|
||||
const index = resolutions.indexOf(resolution);
|
||||
@@ -56,7 +43,7 @@ export const OrderbookControls = ({
|
||||
<button
|
||||
onClick={increaseResolution}
|
||||
disabled={resolutions.indexOf(resolution) >= resolutions.length - 1}
|
||||
className="flex items-center px-2 border-r cursor-pointer border-default"
|
||||
className="flex items-center px-2 border-r cursor-pointer border-default disabled:cursor-default"
|
||||
data-testid="plus-button"
|
||||
>
|
||||
<VegaIcon size={12} name={VegaIconNames.PLUS} />
|
||||
@@ -67,12 +54,14 @@ export const OrderbookControls = ({
|
||||
trigger={
|
||||
<TradingDropdownTrigger data-testid="resolution">
|
||||
<button
|
||||
className="flex items-center px-2 text-left gap-1"
|
||||
className="flex items-center justify-between px-2 gap-1"
|
||||
style={{
|
||||
minWidth: `${
|
||||
Math.max.apply(
|
||||
null,
|
||||
resolutions.map((item) => formatResolution(item).length)
|
||||
resolutions.map(
|
||||
(item) => formatResolution(item, decimalPlaces).length
|
||||
)
|
||||
) + 5
|
||||
}ch`,
|
||||
}}
|
||||
@@ -83,15 +72,19 @@ export const OrderbookControls = ({
|
||||
isOpen ? VegaIconNames.CHEVRON_UP : VegaIconNames.CHEVRON_DOWN
|
||||
}
|
||||
/>
|
||||
{formatResolution(resolution)}
|
||||
{formatResolution(resolution, decimalPlaces)}
|
||||
</button>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent align="start">
|
||||
{resolutions.map((r) => (
|
||||
<TradingDropdownItem key={r} onClick={() => setResolution(r)}>
|
||||
{formatResolution(r)}
|
||||
<TradingDropdownItem
|
||||
key={r}
|
||||
onClick={() => setResolution(r)}
|
||||
className="justify-end"
|
||||
>
|
||||
{formatResolution(r, decimalPlaces)}
|
||||
</TradingDropdownItem>
|
||||
))}
|
||||
</TradingDropdownContent>
|
||||
@@ -99,7 +92,7 @@ export const OrderbookControls = ({
|
||||
<button
|
||||
onClick={decreaseResolution}
|
||||
disabled={resolutions.indexOf(resolution) <= 0}
|
||||
className="flex items-center px-2 cursor-pointer border-x border-default"
|
||||
className="flex items-center px-2 cursor-pointer border-x border-default disabled:cursor-default"
|
||||
data-testid="minus-button"
|
||||
>
|
||||
<VegaIcon size={12} name={VegaIconNames.MINUS} />
|
||||
@@ -107,3 +100,49 @@ export const OrderbookControls = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const formatResolution = (r: number, decimalPlaces: number) => {
|
||||
let num = addDecimalsFormatNumber(r, decimalPlaces);
|
||||
|
||||
// Remove trailing zeroes
|
||||
num = num.replace(/\.?0+$/, '');
|
||||
|
||||
return num;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a list of resolutions based on the largest and smallest
|
||||
* possible values using the last traded price and the market
|
||||
* decimal places
|
||||
*/
|
||||
export const createResolutions = (
|
||||
lastTradedPrice: string,
|
||||
decimalPlaces: number
|
||||
) => {
|
||||
// number of levels determined by either the number
|
||||
// of digits in the last traded price OR the number of decimal
|
||||
// places. For example:
|
||||
//
|
||||
// last traded = 1 (0.001)
|
||||
// dps = 3
|
||||
// result = 3
|
||||
//
|
||||
// last traded = 100001 (1000.01
|
||||
// dps = 2
|
||||
// result = 6
|
||||
const levelCount = Math.max(lastTradedPrice.length ?? 0, decimalPlaces + 1);
|
||||
const generatedResolutions = new Array(levelCount)
|
||||
.fill(null)
|
||||
.map((_, i) => Math.pow(10, i));
|
||||
const customResolutions = [2, 5, 20, 50, 200, 500];
|
||||
const combined = customResolutions.concat(generatedResolutions);
|
||||
combined.sort((a, b) => a - b);
|
||||
|
||||
// Remove any resolutions higher than the generated ones as
|
||||
// we dont want a custom resolution higher than necessary
|
||||
const resolutions = combined.filter((r) => {
|
||||
return r <= generatedResolutions[generatedResolutions.length - 1];
|
||||
});
|
||||
|
||||
return resolutions;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface OrderbookRowData {
|
||||
cumulativeVol: number;
|
||||
}
|
||||
|
||||
export const getPriceLevel = (price: string | bigint, resolution: number) => {
|
||||
export const getPriceLevel = (price: string, resolution: number) => {
|
||||
const p = BigInt(price);
|
||||
const r = BigInt(resolution);
|
||||
let priceLevel = (p / r) * r;
|
||||
@@ -43,7 +43,7 @@ const updateCumulativeVolumeByType = (
|
||||
};
|
||||
|
||||
export const compactRows = (
|
||||
data: PriceLevelFieldsFragment[] | null | undefined,
|
||||
data: PriceLevelFieldsFragment[],
|
||||
dataType: VolumeType,
|
||||
resolution: number
|
||||
) => {
|
||||
|
||||
@@ -13,6 +13,7 @@ interface OrderbookRowProps {
|
||||
cumulativeVolume: number;
|
||||
decimalPlaces: number;
|
||||
positionDecimalPlaces: number;
|
||||
priceFormatDecimalPlaces: number;
|
||||
price: string;
|
||||
onClick: (args: { price?: string; size?: string }) => void;
|
||||
type: VolumeType;
|
||||
@@ -26,6 +27,7 @@ export const OrderbookRow = memo(
|
||||
cumulativeVolume,
|
||||
decimalPlaces,
|
||||
positionDecimalPlaces,
|
||||
priceFormatDecimalPlaces,
|
||||
price,
|
||||
onClick,
|
||||
type,
|
||||
@@ -35,6 +37,7 @@ export const OrderbookRow = memo(
|
||||
const txtId = type === VolumeType.bid ? 'bid' : 'ask';
|
||||
const cols =
|
||||
width >= HIDE_CUMULATIVE_VOL_WIDTH ? 3 : width >= HIDE_VOL_WIDTH ? 2 : 1;
|
||||
|
||||
return (
|
||||
<div className="relative px-1">
|
||||
<CumulationBar
|
||||
@@ -54,7 +57,8 @@ export const OrderbookRow = memo(
|
||||
value={BigInt(price)}
|
||||
valueFormatted={addDecimalsFixedFormatNumber(
|
||||
price,
|
||||
decimalPlaces
|
||||
decimalPlaces,
|
||||
priceFormatDecimalPlaces
|
||||
)}
|
||||
className={classNames({
|
||||
'text-market-red dark:text-market-red': type === VolumeType.ask,
|
||||
|
||||
@@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event';
|
||||
import { generateMockData, VolumeType } from './orderbook-data';
|
||||
import { Orderbook, OrderbookMid } from './orderbook';
|
||||
import * as orderbookData from './orderbook-data';
|
||||
import { createResolutions, formatResolution } from './orderbook-controls';
|
||||
|
||||
function mockOffsetSize(width: number, height: number) {
|
||||
Object.defineProperty(HTMLElement.prototype, 'getBoundingClientRect', {
|
||||
@@ -37,7 +38,8 @@ describe('Orderbook', () => {
|
||||
jest.clearAllMocks();
|
||||
mockOffsetSize(800, 768);
|
||||
});
|
||||
it('markPrice should be in the middle', async () => {
|
||||
|
||||
it('lastTradedPrice should be in the middle', async () => {
|
||||
render(
|
||||
<Orderbook
|
||||
decimalPlaces={decimalPlaces}
|
||||
@@ -71,6 +73,7 @@ describe('Orderbook', () => {
|
||||
expect(
|
||||
await screen.findByTestId(`last-traded-${params.lastTradedPrice}`)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Before resolution change the price is 122.934
|
||||
await userEvent.click(screen.getByTestId('price-122901'));
|
||||
expect(onClickSpy).toBeCalledWith({ price: '122.901' });
|
||||
@@ -86,15 +89,16 @@ describe('Orderbook', () => {
|
||||
expect(orderbookData.compactRows).toHaveBeenCalledWith(
|
||||
mockedData.bids,
|
||||
VolumeType.bid,
|
||||
10
|
||||
2
|
||||
);
|
||||
expect(orderbookData.compactRows).toHaveBeenCalledWith(
|
||||
mockedData.asks,
|
||||
VolumeType.ask,
|
||||
10
|
||||
2
|
||||
);
|
||||
await userEvent.click(screen.getByTestId('price-12294'));
|
||||
expect(onClickSpy).toBeCalledWith({ price: '122.94' });
|
||||
|
||||
await userEvent.click(screen.getByTestId('price-122938'));
|
||||
expect(onClickSpy).toBeCalledWith({ price: '122.938' });
|
||||
});
|
||||
|
||||
it('plus - minus buttons should change resolution', async () => {
|
||||
@@ -114,26 +118,30 @@ describe('Orderbook', () => {
|
||||
1
|
||||
);
|
||||
expect(screen.getByTestId('minus-button')).toBeDisabled();
|
||||
userEvent.click(screen.getByTestId('plus-button'));
|
||||
await userEvent.click(screen.getByTestId('plus-button'));
|
||||
expect((orderbookData.compactRows as jest.Mock).mock.lastCall[2]).toEqual(
|
||||
2
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId('plus-button'));
|
||||
expect((orderbookData.compactRows as jest.Mock).mock.lastCall[2]).toEqual(
|
||||
5
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('minus-button')).not.toBeDisabled();
|
||||
await userEvent.click(screen.getByTestId('minus-button'));
|
||||
await waitFor(() => {
|
||||
expect((orderbookData.compactRows as jest.Mock).mock.lastCall[2]).toEqual(
|
||||
10
|
||||
2
|
||||
);
|
||||
});
|
||||
expect(screen.getByTestId('minus-button')).not.toBeDisabled();
|
||||
userEvent.click(screen.getByTestId('minus-button'));
|
||||
await waitFor(() => {
|
||||
expect((orderbookData.compactRows as jest.Mock).mock.lastCall[2]).toEqual(
|
||||
1
|
||||
);
|
||||
});
|
||||
expect(screen.getByTestId('minus-button')).toBeDisabled();
|
||||
await userEvent.click(screen.getByTestId('resolution'));
|
||||
|
||||
await userEvent.click(screen.getByTestId('resolution'));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('menu')).toBeInTheDocument();
|
||||
});
|
||||
await userEvent.click(screen.getAllByRole('menuitem')[5]);
|
||||
await userEvent.click(screen.getAllByRole('menuitem')[11]);
|
||||
await waitFor(() => {
|
||||
expect((orderbookData.compactRows as jest.Mock).mock.lastCall[2]).toEqual(
|
||||
100000
|
||||
@@ -223,3 +231,58 @@ describe('OrderbookMid', () => {
|
||||
expect(screen.getByTestId('icon-arrow-down')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createResolutions', () => {
|
||||
it('create resolutions relative to the market', () => {
|
||||
expect(
|
||||
createResolutions(
|
||||
'1', // 0.001
|
||||
3
|
||||
)
|
||||
).toEqual([1, 2, 5, 10, 20, 50, 100, 200, 500, 1000]);
|
||||
|
||||
expect(
|
||||
createResolutions(
|
||||
'190017', // 1900.17
|
||||
2
|
||||
)
|
||||
).toEqual([1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 10000, 100000]);
|
||||
|
||||
expect(
|
||||
createResolutions(
|
||||
'123456789', // 1234.56789
|
||||
5
|
||||
)
|
||||
).toEqual([
|
||||
1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 10000, 100000, 1000000,
|
||||
10000000, 100000000,
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes resolutions that arent precise enough for the market', () => {
|
||||
expect(
|
||||
createResolutions(
|
||||
'1', // 0.01
|
||||
2
|
||||
)
|
||||
).toEqual([1, 2, 5, 10, 20, 50, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatResolution', () => {
|
||||
it('formats less than 1', () => {
|
||||
expect(formatResolution(1, 2)).toEqual('0.01');
|
||||
expect(formatResolution(1, 3)).toEqual('0.001');
|
||||
expect(formatResolution(2, 4)).toEqual('0.0002');
|
||||
expect(formatResolution(5, 8)).toEqual('0.00000005');
|
||||
expect(formatResolution(10000, 5)).toEqual('0.1');
|
||||
});
|
||||
|
||||
it('formats greater than 1', () => {
|
||||
expect(formatResolution(1000, 2)).toEqual('10');
|
||||
expect(formatResolution(100000, 4)).toEqual('10');
|
||||
expect(formatResolution(10000000, 2)).toEqual('100,000');
|
||||
expect(formatResolution(500, 2)).toEqual('5');
|
||||
expect(formatResolution(500, 1)).toEqual('50');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ const OrderbookSide = ({
|
||||
type,
|
||||
decimalPlaces,
|
||||
positionDecimalPlaces,
|
||||
priceFormatDecimalPlaces,
|
||||
onClick,
|
||||
width,
|
||||
maxVol,
|
||||
@@ -31,6 +32,7 @@ const OrderbookSide = ({
|
||||
resolution: number;
|
||||
decimalPlaces: number;
|
||||
positionDecimalPlaces: number;
|
||||
priceFormatDecimalPlaces: number;
|
||||
type: VolumeType;
|
||||
onClick: (args: { price?: string; size?: string }) => void;
|
||||
width: number;
|
||||
@@ -53,10 +55,11 @@ const OrderbookSide = ({
|
||||
{rows.map((data) => (
|
||||
<OrderbookRow
|
||||
key={data.price}
|
||||
price={(BigInt(data.price) / BigInt(resolution)).toString()}
|
||||
price={data.price}
|
||||
onClick={onClick}
|
||||
decimalPlaces={decimalPlaces - Math.log10(resolution)}
|
||||
decimalPlaces={decimalPlaces}
|
||||
positionDecimalPlaces={positionDecimalPlaces}
|
||||
priceFormatDecimalPlaces={priceFormatDecimalPlaces}
|
||||
volume={data.volume}
|
||||
cumulativeVolume={data.cumulativeVol}
|
||||
type={type}
|
||||
@@ -165,6 +168,12 @@ export const Orderbook = ({
|
||||
const bestAskPrice = asks[0] ? asks[0].price : '0';
|
||||
const bestBidPrice = bids[0] ? bids[0].price : '0';
|
||||
|
||||
// we'll want to only display a relevant number of dps based on the
|
||||
// current resolution selection
|
||||
const priceFormatDecimalPlaces = Math.ceil(
|
||||
decimalPlaces - Math.log10(resolution)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full text-xs grid grid-rows-[1fr_min-content]">
|
||||
<div>
|
||||
@@ -203,6 +212,7 @@ export const Orderbook = ({
|
||||
resolution={resolution}
|
||||
decimalPlaces={decimalPlaces}
|
||||
positionDecimalPlaces={positionDecimalPlaces}
|
||||
priceFormatDecimalPlaces={priceFormatDecimalPlaces}
|
||||
onClick={onClick}
|
||||
width={width}
|
||||
maxVol={maxVol}
|
||||
@@ -220,6 +230,7 @@ export const Orderbook = ({
|
||||
resolution={resolution}
|
||||
decimalPlaces={decimalPlaces}
|
||||
positionDecimalPlaces={positionDecimalPlaces}
|
||||
priceFormatDecimalPlaces={priceFormatDecimalPlaces}
|
||||
onClick={onClick}
|
||||
width={width}
|
||||
maxVol={maxVol}
|
||||
|
||||
@@ -199,7 +199,7 @@ export const allMarketsWithLiveDataProvider = makeDerivedDataProvider<
|
||||
return data.find(
|
||||
(market) =>
|
||||
market.id ===
|
||||
(parts[1].delta as MarketDataUpdateFieldsFragment).marketId
|
||||
(parts[1].delta as MarketDataUpdateFieldsFragment)?.marketId
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaTransactionStore, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useHasAmendableOrder } from '../../order-hooks';
|
||||
|
||||
@@ -28,12 +28,7 @@ export const OpenOrdersMenu = ({ marketId }: { marketId: string }) => {
|
||||
};
|
||||
|
||||
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={onClick}
|
||||
data-testid="cancelAll"
|
||||
>
|
||||
<TradingButton size="extra-small" onClick={onClick} data-testid="cancelAll">
|
||||
{t('Cancel all')}
|
||||
</TradingButton>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { PartialDeep } from 'type-fest';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { OrderFieldsFragment, OrderListTableProps } from '../';
|
||||
import type { Order, OrderFieldsFragment, OrderListTableProps } from '../';
|
||||
import { OrderListTable } from '../';
|
||||
import {
|
||||
generateOrder,
|
||||
@@ -164,6 +164,24 @@ describe('OrderListTable', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('negative positionDecimalPoints should be properly rendered in size column', async () => {
|
||||
const localMarketOrder = {
|
||||
...marketOrder,
|
||||
size: '3000',
|
||||
market: {
|
||||
...marketOrder.market,
|
||||
positionDecimalPlaces: -4,
|
||||
},
|
||||
} as Order;
|
||||
|
||||
await act(async () => {
|
||||
render(generateJsx({ rowData: [localMarketOrder] }));
|
||||
});
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[2]).toHaveTextContent('+30,000,000');
|
||||
});
|
||||
|
||||
describe('amend cell', () => {
|
||||
it('allows cancelling and editing for permitted orders', async () => {
|
||||
const mockEdit = jest.fn();
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ActionsDropdown,
|
||||
ButtonLink,
|
||||
TradingDropdownCopyItem,
|
||||
DropdownMenuItem,
|
||||
TradingDropdownItem,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
@@ -277,7 +277,7 @@ export const OrderListTable = memo<
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{isOrderAmendable(data) && !props.isReadOnly && (
|
||||
<>
|
||||
{!data.icebergOrder && (
|
||||
@@ -301,14 +301,14 @@ export const OrderListTable = memo<
|
||||
value={data.id}
|
||||
text={t('Copy order ID')}
|
||||
/>
|
||||
<DropdownMenuItem
|
||||
<TradingDropdownItem
|
||||
key={'view-order'}
|
||||
data-testid="view-order"
|
||||
onClick={() => onView(data)}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View order details')}
|
||||
</DropdownMenuItem>
|
||||
</TradingDropdownItem>
|
||||
</ActionsDropdown>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
ButtonLink,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
DropdownMenuItem,
|
||||
TradingDropdownItem,
|
||||
TradingDropdownCopyItem,
|
||||
Pill,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
@@ -246,7 +246,7 @@ export const StopOrdersTable = memo(
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{data.status === Schema.StopOrderStatus.STATUS_PENDING &&
|
||||
!props.isReadOnly && (
|
||||
<ButtonLink
|
||||
@@ -263,7 +263,7 @@ export const StopOrdersTable = memo(
|
||||
value={data.order.id}
|
||||
text={t('Copy order ID')}
|
||||
/>
|
||||
<DropdownMenuItem
|
||||
<TradingDropdownItem
|
||||
key={'view-order'}
|
||||
data-testid="view-order"
|
||||
onClick={() =>
|
||||
@@ -273,7 +273,7 @@ export const StopOrdersTable = memo(
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View order details')}
|
||||
</DropdownMenuItem>
|
||||
</TradingDropdownItem>
|
||||
</ActionsDropdown>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('Positions', () => {
|
||||
'Market',
|
||||
'Size / Notional',
|
||||
'Entry / Mark',
|
||||
'Margin',
|
||||
'Margin / Leverage',
|
||||
'Liquidation',
|
||||
'Realised PNL',
|
||||
'Unrealised PNL',
|
||||
@@ -201,6 +201,19 @@ describe('Positions', () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handle negative positionDecimalPlaces', async () => {
|
||||
await renderComponent({
|
||||
...singleRow,
|
||||
openVolume: '-2000',
|
||||
positionDecimalPlaces: -4,
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const cell = cells[1];
|
||||
expect(within(cell).getByTestId('stack-cell-primary')).toHaveTextContent(
|
||||
'-20,000,000'
|
||||
);
|
||||
});
|
||||
|
||||
describe('PNLCell', () => {
|
||||
const props = {
|
||||
data: undefined,
|
||||
|
||||
@@ -292,7 +292,7 @@ export const PositionsTable = ({
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Margin'),
|
||||
headerName: t('Margin / Leverage'),
|
||||
colId: 'margin',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
@@ -456,7 +456,7 @@ export const PositionsTable = ({
|
||||
...COL_DEFS.actions,
|
||||
cellRenderer: ({ data }: VegaICellRendererParams<Position>) => {
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{data?.openVolume &&
|
||||
data?.openVolume !== '0' &&
|
||||
data.partyId === pubKey ? (
|
||||
@@ -548,9 +548,9 @@ const WarningCell = ({
|
||||
showIcon?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex justify-end items-center">
|
||||
<div className="flex items-center justify-end">
|
||||
{showIcon && (
|
||||
<span className="text-black dark:text-white mr-2">
|
||||
<span className="mr-2 text-black dark:text-white">
|
||||
<VegaIcon name={VegaIconNames.EXCLAIMATION_MARK} size={12} />
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -35,16 +35,13 @@ export const ProposalsList = ({
|
||||
const { columnDefs, defaultColDef } = useColumnDefs();
|
||||
|
||||
return (
|
||||
<div className="relative h-full">
|
||||
<AgGrid
|
||||
className="w-full h-full"
|
||||
columnDefs={columnDefs}
|
||||
rowData={filteredData}
|
||||
defaultColDef={defaultColDef}
|
||||
getRowId={({ data }) => data.id}
|
||||
overlayNoRowsTemplate={t('No markets')}
|
||||
components={{ SuccessorMarketRenderer, MarketNameProposalCell }}
|
||||
/>
|
||||
</div>
|
||||
<AgGrid
|
||||
columnDefs={columnDefs}
|
||||
rowData={filteredData}
|
||||
defaultColDef={defaultColDef}
|
||||
getRowId={({ data }) => data.id}
|
||||
overlayNoRowsTemplate={t('No markets')}
|
||||
components={{ SuccessorMarketRenderer, MarketNameProposalCell }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,6 @@ import { toNumberParts } from '@vegaprotocol/utils';
|
||||
export const useNumberParts = (
|
||||
value: BigNumber | null | undefined,
|
||||
decimals: number
|
||||
): [integers: string, decimalPlaces: string] => {
|
||||
): [integers: string, decimalPlaces: string, separator: string | undefined] => {
|
||||
return useMemo(() => toNumberParts(value, decimals), [decimals, value]);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
export const IconInfo = ({ size = 14 }: { size: number }) => {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<svg width={size} height={size} viewBox="0 0 14 14">
|
||||
<path d="M7 0C3.13 0 0 3.13 0 7C0 10.87 3.13 14 7 14C10.87 14 14 10.87 14 7C14 3.13 10.87 0 7 0ZM7.75 10.75H6.25V5.75H7.75V10.75ZM7.75 4.75H6.25V3.25H7.75V4.75Z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useCallback } from 'react';
|
||||
import { useLayoutEffect } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { Intent } from '../../utils/intent';
|
||||
import { Icon } from '../icon';
|
||||
import { Icon, VegaIcon, VegaIconNames } from '../icon';
|
||||
import { Loader } from '../loader';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
@@ -317,18 +317,14 @@ export const Toast = ({
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="flex relative">
|
||||
<div className="relative flex">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="toast-close"
|
||||
onClick={closeToast}
|
||||
className="absolute p-[8px] top-[3px] right-[3px] z-20"
|
||||
className="absolute top-0 right-0 z-20 flex items-center p-2"
|
||||
>
|
||||
<Icon
|
||||
name="cross"
|
||||
size={3}
|
||||
className="!block dark:text-white !w-[11px] !h-[11px]"
|
||||
/>
|
||||
<VegaIcon name={VegaIconNames.CROSS} size={12} />
|
||||
</button>
|
||||
<div
|
||||
data-testid="toast-accent"
|
||||
@@ -348,7 +344,7 @@ export const Toast = ({
|
||||
// red
|
||||
'bg-vega-red-500 text-vega-red-600': intent === Intent.Danger,
|
||||
},
|
||||
'w-8 p-[9px]',
|
||||
'w-8 p-2',
|
||||
'flex justify-center'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { usePrevious } from '@vegaprotocol/react-helpers';
|
||||
import classNames from 'classnames';
|
||||
import type { Ref } from 'react';
|
||||
import { useLayoutEffect, useRef } from 'react';
|
||||
import { Button } from '../button';
|
||||
import { TradingButton } from '../trading-button';
|
||||
import { Toast } from './toast';
|
||||
import type { Toasts } from './use-toasts';
|
||||
import { ToastPosition, useToasts, useToastsConfiguration } from './use-toasts';
|
||||
@@ -87,26 +87,27 @@ export const ToastsContainer = ({
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
<Button
|
||||
title={t('Dismiss all toasts')}
|
||||
size="sm"
|
||||
fill={true}
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute top-[-38px] right-0 z-20',
|
||||
'absolute w-full top-[-38px] right-0 z-20',
|
||||
'transition-opacity',
|
||||
'opacity-0 group-hover:opacity-50 hover:!opacity-100',
|
||||
'text-sm text-black dark:text-white bg-white dark:bg-black hover:!bg-white hover:dark:!bg-black',
|
||||
{
|
||||
hidden: validToasts.length === 0,
|
||||
}
|
||||
)}
|
||||
onClick={() => {
|
||||
closeAll();
|
||||
}}
|
||||
variant={'default'}
|
||||
>
|
||||
{t('Dismiss all')}
|
||||
</Button>
|
||||
<TradingButton
|
||||
title={t('Dismiss all toasts')}
|
||||
size="small"
|
||||
fill={true}
|
||||
onClick={() => {
|
||||
closeAll();
|
||||
}}
|
||||
>
|
||||
{t('Dismiss all')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
</ul>
|
||||
</Portal>
|
||||
);
|
||||
|
||||
@@ -146,7 +146,7 @@ export const TradingDropdownItemIndicator = forwardRef<
|
||||
<DropdownMenuPrimitive.ItemIndicator
|
||||
{...itemIndicatorProps}
|
||||
ref={forwardedRef}
|
||||
className="flex-end text-vega-green"
|
||||
className="flex-end text-vega-green-600 dark:text-vega-green"
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.TICK} />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
|
||||
@@ -84,10 +84,8 @@ const getAffixElement = ({
|
||||
'absolute z-10 top-0 bottom-0 flex items-center',
|
||||
{
|
||||
'fill-black dark:fill-white': prependIconName || appendIconName,
|
||||
'left-3': prependIconName,
|
||||
'right-3': appendIconName,
|
||||
'left-1': prependElement,
|
||||
'right-1': appendElement,
|
||||
'left-3': prependIconName || prependElement,
|
||||
'right-3': appendIconName || appendElement,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Ref, SelectHTMLAttributes } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from '..';
|
||||
import { Icon, VegaIcon, VegaIconNames } from '..';
|
||||
import { defaultSelectElement } from '../../utils/shared';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface TradingSelectProps
|
||||
|
||||
export const TradingSelect = forwardRef<HTMLSelectElement, TradingSelectProps>(
|
||||
({ className, hasError, ...props }, ref) => (
|
||||
<div className="flex items-center relative">
|
||||
<div className="relative flex items-center">
|
||||
<select
|
||||
ref={ref}
|
||||
{...props}
|
||||
@@ -26,10 +26,9 @@ export const TradingSelect = forwardRef<HTMLSelectElement, TradingSelectProps>(
|
||||
'appearance-none rounded-md'
|
||||
)}
|
||||
/>
|
||||
<Icon
|
||||
name="chevron-down"
|
||||
className="absolute right-4 z-10 pointer-events-none"
|
||||
/>
|
||||
<span className="absolute z-10 flex items-center pointer-events-none right-2">
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
@@ -52,7 +51,7 @@ export const TradingRichSelect = forwardRef<
|
||||
return (
|
||||
<div
|
||||
ref={containerRef as Ref<HTMLDivElement>}
|
||||
className="flex items-center relative"
|
||||
className="relative flex items-center"
|
||||
>
|
||||
<SelectPrimitive.Root {...props} defaultOpen={false}>
|
||||
<SelectPrimitive.Trigger
|
||||
@@ -85,11 +84,11 @@ export const TradingRichSelect = forwardRef<
|
||||
side={'bottom'}
|
||||
align={'center'}
|
||||
>
|
||||
<SelectPrimitive.ScrollUpButton className="flex items-center justify-center py-1 absolute w-full h-6 z-20 bg-gradient-to-t from-transparent to-neutral-50 dark:to-neutral-900">
|
||||
<SelectPrimitive.ScrollUpButton className="absolute z-20 flex items-center justify-center w-full h-6 py-1 bg-gradient-to-t from-transparent to-neutral-50 dark:to-neutral-900">
|
||||
<Icon name="chevron-up" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
<SelectPrimitive.Viewport>{children}</SelectPrimitive.Viewport>
|
||||
<SelectPrimitive.ScrollDownButton className="flex items-center justify-center py-1 absolute bottom-0 w-full h-6 z-20 bg-gradient-to-b from-transparent to-neutral-50 dark:to-neutral-900">
|
||||
<SelectPrimitive.ScrollDownButton className="absolute bottom-0 z-20 flex items-center justify-center w-full h-6 py-1 bg-gradient-to-b from-transparent to-neutral-50 dark:to-neutral-900">
|
||||
<Icon name="chevron-down" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
</SelectPrimitive.Content>
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
formatNumberPercentage,
|
||||
getUnlimitedThreshold,
|
||||
isNumeric,
|
||||
removeDecimal,
|
||||
toBigNum,
|
||||
quantumDecimalPlaces,
|
||||
toDecimal,
|
||||
toNumberParts,
|
||||
@@ -84,17 +86,17 @@ describe('number utils', () => {
|
||||
|
||||
describe('toNumberParts', () => {
|
||||
it.each([
|
||||
{ v: null, d: 3, o: ['0', '000'] },
|
||||
{ v: undefined, d: 3, o: ['0', '000'] },
|
||||
{ v: new BigNumber(123), d: 3, o: ['123', '00'] },
|
||||
{ v: new BigNumber(123.123), d: 3, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 6, o: ['123', '123'] },
|
||||
{ v: new BigNumber(123.123), d: 0, o: ['123', ''] },
|
||||
{ v: new BigNumber(123), d: undefined, o: ['123', '00'] },
|
||||
{ v: null, d: 3, o: ['0', '000', '.'] },
|
||||
{ v: undefined, d: 3, o: ['0', '000', '.'] },
|
||||
{ v: new BigNumber(123), d: 3, o: ['123', '00', '.'] },
|
||||
{ v: new BigNumber(123.123), d: 3, o: ['123', '123', '.'] },
|
||||
{ v: new BigNumber(123.123), d: 6, o: ['123', '123', '.'] },
|
||||
{ v: new BigNumber(123.123), d: 0, o: ['123', '', '.'] },
|
||||
{ v: new BigNumber(123), d: undefined, o: ['123', '00', '.'] },
|
||||
{
|
||||
v: new BigNumber(30000),
|
||||
d: undefined,
|
||||
o: ['30,000', '00'],
|
||||
o: ['30,000', '00', '.'],
|
||||
},
|
||||
])('returns correct tuple given the different arguments', ({ v, d, o }) => {
|
||||
expect(toNumberParts(v, d)).toStrictEqual(o);
|
||||
@@ -153,10 +155,36 @@ describe('number utils', () => {
|
||||
{ v: 7, o: '0.0000001' },
|
||||
{ v: 8, o: '0.00000001' },
|
||||
{ v: 9, o: '0.000000001' },
|
||||
{ v: -1, o: '10' },
|
||||
{ v: -2, o: '100' },
|
||||
{ v: -3, o: '1000' },
|
||||
])('formats with toNumber given number correctly', ({ v, o }) => {
|
||||
expect(toDecimal(v)).toStrictEqual(o);
|
||||
});
|
||||
});
|
||||
|
||||
describe('positive and negative decimals should be handled correctly', () => {
|
||||
const baseNum = '2000';
|
||||
const methods = [removeDecimal, toBigNum];
|
||||
it.each([
|
||||
{ decimals: 0, result: ['2000', '2000'] },
|
||||
{ decimals: 1, result: ['20000', '200'] },
|
||||
{ decimals: -1, result: ['200', '20000'] },
|
||||
{ decimals: 2, result: ['200000', '20'] },
|
||||
{ decimals: -2, result: ['20', '200000'] },
|
||||
{ decimals: 3, result: ['2000000', '2'] },
|
||||
{ decimals: -3, result: ['2', '2000000'] },
|
||||
{ decimals: 4, result: ['20000000', '0.2'] },
|
||||
{ decimals: -4, result: ['0', '20000000'] }, // removeDecimal has toFixed(0) at the end
|
||||
])(
|
||||
'number methods should handle negative decimals',
|
||||
({ decimals, result }) => {
|
||||
methods.forEach((method, i) => {
|
||||
expect(method(baseNum, decimals).toString()).toEqual(result[i]);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('quantumDecimalPlaces', () => {
|
||||
|
||||
@@ -21,7 +21,7 @@ const MAX_FRACTION_DIGITS = 20;
|
||||
|
||||
export function toDecimal(numberOfDecimals: number) {
|
||||
return new BigNumber(1)
|
||||
.dividedBy(Math.pow(10, numberOfDecimals))
|
||||
.dividedBy(new BigNumber(10).exponentiatedBy(numberOfDecimals))
|
||||
.toString(10);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ export function toBigNum(
|
||||
rawValue: string | number,
|
||||
decimals: number
|
||||
): BigNumber {
|
||||
return new BigNumber(rawValue || 0).dividedBy(Math.pow(10, decimals));
|
||||
const divides = new BigNumber(10).exponentiatedBy(decimals);
|
||||
return new BigNumber(rawValue || 0).dividedBy(divides);
|
||||
}
|
||||
|
||||
export function addDecimal(
|
||||
@@ -48,7 +49,8 @@ export function removeDecimal(
|
||||
value: string | BigNumber,
|
||||
decimals: number
|
||||
): string {
|
||||
return new BigNumber(value || 0).times(Math.pow(10, decimals)).toFixed(0);
|
||||
const times = new BigNumber(10).exponentiatedBy(decimals);
|
||||
return new BigNumber(value || 0).times(times).toFixed(0);
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
|
||||
@@ -163,15 +165,15 @@ export const formatNumberPercentage = (value: BigNumber, decimals?: number) => {
|
||||
export const toNumberParts = (
|
||||
value: BigNumber | null | undefined,
|
||||
decimals = 18
|
||||
): [integers: string, decimalPlaces: string] => {
|
||||
): [integers: string, decimalPlaces: string, separator: string] => {
|
||||
if (!value) {
|
||||
return ['0', '0'.repeat(decimals)];
|
||||
return ['0', '0'.repeat(decimals), '.'];
|
||||
}
|
||||
const separator = getDecimalSeparator() || '.';
|
||||
const [integers, decimalsPlaces] = formatNumber(value, decimals)
|
||||
.toString()
|
||||
.split(separator);
|
||||
return [integers, decimalsPlaces || ''];
|
||||
return [integers, decimalsPlaces || '', separator];
|
||||
};
|
||||
|
||||
export const isNumeric = (
|
||||
|
||||
@@ -4,6 +4,12 @@ export const validateAmount = (step: number | string, field: string) => {
|
||||
const [, stepDecimals = ''] = String(step).split('.');
|
||||
|
||||
return (value?: string) => {
|
||||
if (Number(step) > 1) {
|
||||
if (Number(value) % Number(step) > 0) {
|
||||
return t(`${field} must be a multiple of ${step} for this market`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const [, valueDecimals = ''] = (value || '').split('.');
|
||||
if (stepDecimals.length < valueDecimals.length) {
|
||||
if (stepDecimals === '') {
|
||||
|
||||
@@ -598,7 +598,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
|
||||
|
||||
if (isWithdrawTransaction(tx.body)) {
|
||||
const completeWithdrawalButton = tx.withdrawal && (
|
||||
<p className="mt-1">
|
||||
<p className="mt-2">
|
||||
<Button
|
||||
data-testid="toast-complete-withdrawal"
|
||||
size="xs"
|
||||
|
||||
@@ -211,7 +211,7 @@ const MultipleReadyToWithdrawToastContent = ({
|
||||
count.toString()
|
||||
)}
|
||||
</p>
|
||||
<p className="mt-1">
|
||||
<p className="mt-2">
|
||||
<Button
|
||||
data-testid="toast-view-withdrawals"
|
||||
size="xs"
|
||||
@@ -243,7 +243,7 @@ const SingleReadyToWithdrawToastContent = ({
|
||||
},
|
||||
});
|
||||
const completeButton = (
|
||||
<p className="mt-1">
|
||||
<p className="mt-2">
|
||||
<Button
|
||||
data-testid="toast-complete-withdrawal"
|
||||
size="xs"
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
Button,
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
TradingInputError,
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
TradingRichSelect,
|
||||
ExternalLink,
|
||||
Intent,
|
||||
TradingButton,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import BigNumber from 'bignumber.js';
|
||||
@@ -183,7 +183,7 @@ export const WithdrawForm = ({
|
||||
<>
|
||||
<div className="mb-4 text-sm">
|
||||
<p>{t('There are two steps required to make a withdrawal')}</p>
|
||||
<ol className="list-disc pl-4">
|
||||
<ol className="pl-4 list-disc">
|
||||
<li>{t('Step 1 - Release funds from Vega')}</li>
|
||||
<li>{t('Step 2 - Transfer funds to your Ethereum wallet')}</li>
|
||||
</ol>
|
||||
@@ -289,14 +289,13 @@ export const WithdrawForm = ({
|
||||
</div>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<Button
|
||||
<TradingButton
|
||||
data-testid="submit-withdrawal"
|
||||
type="submit"
|
||||
variant="primary"
|
||||
fill={true}
|
||||
>
|
||||
Release funds
|
||||
</Button>
|
||||
{t('Release funds')}
|
||||
</TradingButton>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
@@ -309,7 +308,7 @@ const UseButton = (props: UseButtonProps) => {
|
||||
<button
|
||||
{...props}
|
||||
type="button"
|
||||
className="ml-auto text-sm absolute top-0 right-0 underline"
|
||||
className="absolute top-0 right-0 ml-auto text-sm underline"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user