Compare commits

...
Author SHA1 Message Date
Matthew Russell aef44b228c chore: update margin and leverage table heading for positions table 2023-09-04 16:53:06 -07:00
Matthew Russell 2af3cc3e94 chore: use tab menu for propose link 2023-09-04 16:24:34 -07:00
Matthew Russell 450b46343e chore: adjust telemetry toast ui 2023-09-04 16:18:32 -07:00
Matthew Russell e79d6c6e9e test: update deposit submit button selector 2023-09-04 16:02:31 -07:00
Matthew Russell 9d3d7af118 chore: nudge spacing in withdrawals toasts 2023-09-04 15:51:46 -07:00
Matthew Russell fecea28207 chore: switch toast container to use trading variant 2023-09-04 15:43:45 -07:00
Matthew Russell 7ba11b8ecd chore: switch ledger export button 2023-09-04 15:36:53 -07:00
Matthew Russell 1a27b19670 chore: switch accounts table button to use trading variant 2023-09-04 15:35:25 -07:00
Matthew Russell ded53f9f19 chore: remove unused component 2023-09-04 15:27:42 -07:00
Matthew Russell 45cf0b2866 chore: use trading buttons for sidebar submits 2023-09-04 15:26:25 -07:00
Matthew Russell 0814d6d991 chore: use vega cross for tooltip close icon 2023-09-04 15:19:35 -07:00
Matthew Russell 05f450b498 chore: switch dropdowns to use right version 2023-09-04 15:09:09 -07:00
Matthew Russell 0e46887479 chore: use correct dropdowns for account history, remove useEffect 2023-09-04 15:04:46 -07:00
Matthew Russell c85ad37925 chore: show macd and moving average by default 2023-09-04 14:42:16 -07:00
Matthew Russell 4f94151e61 chore: darken green check in dropdown 2023-09-04 14:42:16 -07:00
Matthew Russell b80810380f chore: change asset detail dialog to use vega icon 2023-09-04 14:42:16 -07:00
Matthew Russell a86cb31493 chore: switch copy icon in asset detail dialog 2023-09-04 14:42:16 -07:00
Matthew Russell d772972267 chore: change select to use vega chevron 2023-09-04 14:42:16 -07:00
m.rayandMatthew Russell d268088e60 chore(trading): update dropdowns (#4694)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-09-04 20:25:24 +00:00
Matthew Russell 1208d3f2a8 feat(market-depth): additional orderbook grouping levels (#4658) 2023-09-04 07:47:27 -07:00
Sam Keen b5ba4f99d9 fix(governance,utils,react-helpers): token locale formatting issue (#4678) 2023-09-04 12:09:38 +01:00
Joe Tsang 66f25603d3 test(explorer): add e2e test for explorer oracles (#4657) 2023-09-04 09:41:04 +00:00
Edd 4afd469404 chore(explorer,trading,governance): reconfigure hooks (#4655) 2023-09-04 09:36:13 +00:00
Maciek 9e5bc9c8d1 chore(trading): handle negative decimals (#4659) 2023-09-04 09:46:02 +02:00
Matthew Russell e41aff88b1 chore(trading): make top traded default sort for market selector (#4637) 2023-09-03 11:15:41 -07:00
Matthew Russell 9209074332 chore(trading): change pane context buttons color (#4680) 2023-09-01 17:00:11 -07:00
68 changed files with 910 additions and 742 deletions
-3
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Lint all staged files
yarn lint-staged
+8
View File
@@ -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 -19
View File
@@ -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>
);
+15 -11
View File
@@ -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 -8
View File
@@ -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>
+4 -3
View File
@@ -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 -6
View File
@@ -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'
);
});
});
+61 -51
View File
@@ -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';
+1 -3
View File
@@ -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({
+9 -10
View File
@@ -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')}
+21 -1
View File
@@ -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 = {
+3 -4
View File
@@ -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;
};
+2 -2
View File
@@ -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
) => {
+5 -1
View File
@@ -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,
+79 -16
View File
@@ -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');
});
});
+13 -2
View File
@@ -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}
+1 -1
View File
@@ -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,
+4 -4
View File
@@ -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>
+36 -8
View File
@@ -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', () => {
+8 -6
View File
@@ -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"
+6 -7
View File
@@ -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"
/>
);
};