Compare commits

...
Author SHA1 Message Date
Matthew Russell dd4369978f test: add e2e tests to check resource links are rendered 2023-03-22 15:50:06 -07:00
Matthew Russell 06ab835363 chore: lift condition to check for link presence to avoid an empty dropdown 2023-03-22 15:19:48 -07:00
Matthew Russell 348c679793 feat: add feedback links to env, add resources dropdown to nav 2023-03-21 15:20:30 -07:00
dexturr 965f7f6e2d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-21 18:08:39 +00:00
Ciaran McGhie 18633efec3 chore(ui-toolkit): rework intent colours (#3202) 2023-03-21 16:40:39 +00:00
a0b758334a fix(trading): auction triggers liquidity API change in develop for core 0.69.0 (#3197)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2023-03-21 09:19:21 -07:00
Bartłomiej Głownia 9f3026aa4b fix(market-depth): fix depth chart data upate, use market depth as mid price source (#3242) 2023-03-21 08:46:04 -07:00
Maciek faa359e2fb chore(trading): 2764 link to mainnet incident reports (#3235) 2023-03-21 08:19:54 -07:00
Bartłomiej Głownia 1d178b940f feat(trading): store current tab selection (#3148) 2023-03-21 15:50:31 +01:00
dexturr b2dd053621 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-21 12:10:18 +00:00
dexturr 77f5193b7a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-21 06:07:47 +00:00
dexturr 055a0070db chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-21 00:13:48 +00:00
dexturr 29b221c86a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-20 18:09:57 +00:00
Dexter Edwards 3e619acd4b feat(ui-toolkit): button icons (#3233) 2023-03-20 14:40:04 +00:00
botond c720719ac5 chore(governance): update announcement banner text (#3234) 2023-03-20 13:52:03 +00:00
Art c676a7c1e2 chore(explorer): move banner up (#3237) 2023-03-20 13:38:31 +00:00
dexturr ca63a671c8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-20 12:09:43 +00:00
Maciek 4165f3905f chore(static): update domains in network conf files (#3219) 2023-03-20 09:58:05 +00:00
dexturr a359ccb755 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-20 06:07:41 +00:00
dexturr e584737005 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-20 00:17:40 +00:00
dexturr b12c5ed39f chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-19 18:08:07 +00:00
dexturr d3dbd64cf1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-19 12:07:36 +00:00
dexturr 2df62bca61 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-19 06:07:48 +00:00
dexturr fd2a434b46 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-19 00:17:10 +00:00
dexturr 4e7a78f988 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-18 18:08:23 +00:00
dexturr 327619652a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-18 12:07:24 +00:00
dexturr d2339205cc chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-18 06:06:50 +00:00
dexturr 63df4cf465 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-18 00:13:54 +00:00
dexturr 5e34178291 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-17 18:06:54 +00:00
dexturr 0c44664aa5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-17 12:07:53 +00:00
Bartłomiej Głownia 7ef1d75529 chore(trading): remove red background from trades table (#3214) 2023-03-17 09:52:40 +00:00
Maciek 84795b2d6a chore(trading): split bottom panel into two parts (#3205) 2023-03-17 09:50:43 +00:00
dexturr 300019f108 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-17 06:09:09 +00:00
dexturr 2d0aebd3fe chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-17 00:14:43 +00:00
dexturr 9e86d2aff5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-16 18:09:41 +00:00
daro-maj 0f7e76ae01 test(trading): increase coverage for 0004-ewal (#3210) 2023-03-16 13:29:40 +01:00
dexturr 56732d1503 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-16 12:08:59 +00:00
Joe Tsang 5c19263653 chore(governance): include slippage factor to proposals (#3204) 2023-03-16 10:41:18 +00:00
dexturr 0d911b3029 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-16 06:08:43 +00:00
Matthew RussellandEdd 9f059e7862 chore(explorer,governance): update be endpoints (#3183)
Co-authored-by: Edd <edd@vega.xyz>
2023-03-15 20:12:04 -07:00
dexturr 92ea93dadb chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-16 00:16:50 +00:00
80 changed files with 2251 additions and 1270 deletions
+3 -3
View File
@@ -2,11 +2,11 @@
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.xyz
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.xyz/rest
NX_TENDERMINT_URL=https://tm-be.validators-testnet.vega.rocks
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
+1 -1
View File
@@ -71,8 +71,8 @@ function App() {
)}
>
<div>
<Header />
<MainnetSimAd />
<Header />
</div>
<div>
<Main />
@@ -10,7 +10,7 @@ export const Footer = () => {
const [nodeSwitcherOpen, setNodeSwitcherOpen] = useState(false);
const { screenSize } = useScreenDimensions();
const showFullFeedbackLabel = useMemo(
() => ['lg', 'xl'].includes(screenSize),
() => ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize),
[screenSize]
);
@@ -2,6 +2,8 @@
"changes": {
"decimalPlaces": "5",
"positionDecimalPlaces": "5",
"linearSlippageFactor": "0.001",
"quadraticSlippageFactor": "0",
"lpPriceRange": "10",
"instrument": {
"name": "Token test market",
@@ -1,5 +1,7 @@
{
"lpPriceRange": "10",
"linearSlippageFactor": "0.001",
"quadraticSlippageFactor": "0",
"instrument": {
"code": "TEST.24h",
"future": {
@@ -110,10 +110,9 @@ Cypress.Commands.add('vega_wallet_teardown', function () {
}
});
cy.get(vegaWalletContainer).within(() => {
cy.get('[data-testid="associated-amount"]', { timeout: 30000 }).should(
'contain.text',
'0.00'
);
cy.get('[data-testid="vega-wallet-balance-unstaked"]', {
timeout: 30000,
}).should('contain.text', '0.00');
});
});
+1 -1
View File
@@ -2,7 +2,7 @@
NX_VEGA_ENV=VALIDATOR_TESTNET
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_URL=https://api-validators-testnet.vega.rocks/graphql
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/
NX_VEGA_REST=https://api-validators-testnet.vega.rocks/
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
@@ -21,9 +21,9 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
<>
<AnnouncementBanner>
<div className="font-alpha calt uppercase text-center text-lg text-white">
<span className="pr-4">Mainnet sim 3 is live!</span>
<ExternalLink href="https://fairground.wtf/">
Come help stress test the network
<span className="pr-4">Wait no longer, SIM3 is here!</span>
<ExternalLink href="https://fairground.wtf/sim3">
Learn more
</ExternalLink>
</div>
</AnnouncementBanner>
@@ -2,7 +2,7 @@ import type { Dispatch, SetStateAction } from 'react';
import { forwardRef, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Button } from '@vegaprotocol/ui-toolkit';
import { Button, Icon } from '@vegaprotocol/ui-toolkit';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { BigNumber } from '../../../../lib/bignumber';
@@ -87,7 +87,12 @@ const TopThirdCellRenderer = (
<div className="mb-4">
<Button
data-testid="show-all-validators"
rightIcon="arrow-right"
rightIcon={
<Icon
name="arrow-right"
className="fill-current mr-2 align-text-top"
/>
}
className="inline-flex items-center"
>
{t('Reveal top validators')}
@@ -5,6 +5,7 @@ import * as Schema from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
import { Indicator } from '../indicator';
import type { AuctionTrigger } from '@vegaprotocol/types';
export const Status = ({
tradingMode,
@@ -31,7 +32,8 @@ export const Status = ({
};
const status = getStatus();
const tooltipDescription = t(getTooltipDescription(status));
const tooltipDescription =
tradingMode && getTooltipDescription(tradingMode, trigger);
return (
<div>
@@ -52,24 +54,43 @@ export const Status = ({
);
};
const getTooltipDescription = (status: string) => {
let tooltipDescription = '';
const getTooltipDescription = (
status: Schema.MarketTradingMode,
trigger?: Schema.AuctionTrigger
) => {
switch (status) {
case Schema.MarketTradingModeMapping.TRADING_MODE_CONTINUOUS:
tooltipDescription =
'This is the standard trading mode where trades are executed whenever orders are received';
break;
case `${Schema.MarketTradingModeMapping.TRADING_MODE_MONITORING_AUCTION} - ${Schema.AuctionTriggerMapping.AUCTION_TRIGGER_LIQUIDITY}`:
tooltipDescription =
'This market is in auction until it reaches sufficient liquidity';
break;
case Schema.MarketTradingModeMapping.TRADING_MODE_OPENING_AUCTION:
tooltipDescription =
'This is a new market in an opening auction to determine a fair mid-price before starting continuous trading.';
break;
case Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS:
return t(
'This is the standard trading mode where trades are executed whenever orders are received'
);
case Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION:
return getMonitoringDescriptionTooltip(trigger);
case Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION:
return t(
'This is a new market in an opening auction to determine a fair mid-price before starting continuous trading.'
);
default:
break;
return '';
}
};
const getMonitoringDescriptionTooltip = (trigger?: AuctionTrigger) => {
switch (trigger) {
case Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET:
return t(
`This market is in auction until it reaches sufficient liquidity.`
);
case Schema.AuctionTrigger.AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS:
return t(
`This market may have sufficient liquidity but there are not enough priced limit orders in the order book, which are required to deploy liquidity commitment pegged orders.`
);
case Schema.AuctionTrigger.AUCTION_TRIGGER_PRICE:
return t(`This market is in auction due to high price volatility.`);
case Schema.AuctionTrigger.AUCTION_TRIGGER_OPENING:
return t(
`This is a new market in an opening auction to determine a fair mid-price before starting continuous trading`
);
default:
return '';
}
return tooltipDescription;
};
+1 -1
View File
@@ -1,3 +1,3 @@
{
"hosts": ["https://api.n04.d.vega.xyz/graphql"]
"hosts": ["https://api-n04.d.vega.rocks/graphql"]
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,3 +1,3 @@
{
"hosts": ["https://api.n00.mainnet-mirror.vega.xyz/graphql"]
"hosts": ["https://api-n00.mainnet-mirror.vega.rocks/graphql"]
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
"hosts": ["https://api.n01.sandbox.vega.xyz/graphql"]
"hosts": ["https://api-n01.sandbox.vega.rocks/graphql"]
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
"hosts": ["https://api.n00.stagnet1.vega.xyz/graphql"]
"hosts": ["https://api-n00.stagnet1.vega.rocks/graphql"]
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
"hosts": ["https://api.stagnet3.vega.xyz/graphql"]
"hosts": ["https://api-stagnet3.vega.rocks/graphql"]
}
+7 -7
View File
@@ -1,11 +1,11 @@
{
"hosts": [
"https://api.n06.testnet.vega.xyz/graphql",
"https://api.n07.testnet.vega.xyz/graphql",
"https://api.n08.testnet.vega.xyz/graphql",
"https://api.n09.testnet.vega.xyz/graphql",
"https://api.n10.testnet.vega.xyz/graphql",
"https://api.n11.testnet.vega.xyz/graphql",
"https://api.n12.testnet.vega.xyz/graphql"
"https://api-n06.testnet.vega.rocks/graphql",
"https://api-n07.testnet.vega.rocks/graphql",
"https://api-n08.testnet.vega.rocks/graphql",
"https://api-n09.testnet.vega.rocks/graphql",
"https://api-n10.testnet.vega.rocks/graphql",
"https://api-n11.testnet.vega.rocks/graphql",
"https://api-n12.testnet.vega.rocks/graphql"
]
}
+1
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=http://localhost:8545
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=''
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
@@ -75,7 +75,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
validateMarketDataRow(3, 'Quote Name', 'BTC');
});
it('settlement asset displayed', () => {
// need to check why data are not visible
it.skip('settlement asset displayed', () => {
cy.getByTestId(marketTitle).contains('Settlement asset').click();
cy.window().then((win) => {
cy.stub(win, 'prompt').returns('DISABLED WINDOW PROMPT');
@@ -21,7 +21,7 @@ describe('Market proposal notification', { tags: '@smoke' }, () => {
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
cy.mockGQL((req) => {
aliasGQLQuery(
@@ -62,7 +62,7 @@ describe('Market trading page', () => {
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
cy.mockSubscription();
cy.visit('/#/markets/market-0');
@@ -148,7 +148,7 @@ describe('Market trading page', () => {
cy.getByTestId(itemHeader).should('have.text', 'Trading mode');
cy.getByTestId(itemValue).should(
'have.text',
'Monitoring auction - liquidity'
'Monitoring auction - liquidity (target not met)'
);
});
});
@@ -216,6 +216,60 @@ describe('Market trading page', () => {
});
});
});
describe('market bottom panel', { tags: '@smoke' }, () => {
it('on xxl screen should be splitted out into two tables', () => {
cy.getByTestId('tab-positions').should(
'have.attr',
'data-state',
'active'
);
cy.getByTestId('tab-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'inactive'
);
cy.viewport(1801, 1000);
cy.getByTestId('tab-positions').should(
'have.attr',
'data-state',
'active'
);
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'active');
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('Fills').click();
cy.getByTestId('Collateral').click();
cy.getByTestId('tab-positions').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'active');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'active'
);
});
});
});
describe('market states not accepting orders', { tags: '@smoke' }, function () {
@@ -12,7 +12,7 @@ describe('markets table', { tags: '@smoke' }, () => {
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
cy.mockSubscription();
cy.visit('/');
@@ -25,6 +25,23 @@ describe('Desktop view', { tags: '@smoke' }, () => {
});
});
});
it('Resources dropdown should be correctly rendered', () => {
const resourceSelector = 'ul li:last-child';
['Docs', 'Give Feedback'].forEach((text, index) => {
cy.get('nav').find(resourceSelector).contains('Resources').click();
cy.get('nav')
.find(resourceSelector)
.find('.navigation-content li')
.eq(index)
.find('a')
.then((element) => {
expect(element.attr('target')).to.eq('_blank');
expect(element.attr('href')).to.not.be.empty;
expect(element.text()).to.eq(text);
});
});
});
});
});
@@ -161,7 +161,7 @@ describe(
cy.mockTradingPage(
Schema.MarketState.STATE_SUSPENDED,
Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION,
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
cy.mockSubscription();
cy.visit('/#/markets/market-0');
@@ -230,7 +230,7 @@ describe(
cy.mockTradingPage(
Schema.MarketState.STATE_SUSPENDED,
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
cy.mockSubscription();
cy.visit('/#/markets/market-0');
@@ -299,7 +299,7 @@ describe(
cy.mockTradingPage(
Schema.MarketState.STATE_SUSPENDED,
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
cy.mockSubscription();
cy.visit('/#/markets/market-0');
@@ -585,7 +585,7 @@ describe('suspended market validation', { tags: '@regression' }, () => {
cy.mockTradingPage(
Schema.MarketState.STATE_SUSPENDED,
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
const accounts = accountsQuery();
cy.mockGQL((req) => {
@@ -0,0 +1,58 @@
import { connectEthereumWallet } from '../support/ethereum-wallet';
const connectEthWalletBtn = 'connect-eth-wallet-btn';
describe('ethereum wallet', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click();
});
it('can connect', () => {
// 0004-EWAL-001
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-MetaMask').click();
cy.getByTestId('web3-connector-list').should('not.exist');
cy.getByTestId('tab-deposits').should('not.be.empty');
});
it('should see an option to cancel the attempted connection', () => {
// 0004-EWAL-003
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-WalletConnect').click();
cy.get('#walletconnect-qrcode-text').should('exist');
cy.get('#walletconnect-qrcode-close').click();
});
it('able to disconnect eth wallet', () => {
// 0004-EWAL-004
// 0004-EWAL-005
// 0004-EWAL-006
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('MetaMask');
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
cy.getByTestId('disconnect-ethereum-wallet')
.should('have.text', 'Disconnect')
.click();
cy.getByTestId(connectEthWalletBtn).should('exist');
});
});
@@ -1,7 +1,8 @@
import { mockConnectWallet } from '@vegaprotocol/cypress';
import { connectEthereumWallet } from '../support/ethereum-wallet';
import {
mockConnectWallet,
mockConnectWalletWithUserError,
} from '@vegaprotocol/cypress';
const connectEthWalletBtn = 'connect-eth-wallet-btn';
const connectVegaBtn = 'connect-vega-wallet';
const manageVegaBtn = 'manage-vega-wallet';
const form = 'rest-connector-form';
@@ -127,6 +128,21 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
cy.getByTestId(manageVegaBtn).should('exist');
});
it('can not connect', () => {
// 0002-WCON-002
// 0002-WCON-005
// 0002-WCON-007
mockConnectWalletWithUserError();
cy.getByTestId(connectVegaBtn).click();
cy.getByTestId('connectors-list')
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.getByTestId('dialog-content')
.should('contain.text', 'User error')
.and('contain.text', 'the user rejected the wallet connection');
});
it('can change selected public key and disconnect', () => {
// 0002-WCON-022
// 0002-WCON-023
@@ -164,39 +180,3 @@ describe('connect vega wallet', { tags: '@smoke' }, () => {
);
});
});
describe('ethereum wallet', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click();
});
it('can connect', () => {
cy.wait('@NetworkParams');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-MetaMask').click();
cy.getByTestId('web3-connector-list').should('not.exist');
cy.getByTestId('tab-deposits').should('not.be.empty');
});
it('able to disconnect eth wallet', () => {
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('MetaMask');
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
cy.getByTestId('disconnect-ethereum-wallet')
.should('have.text', 'Disconnect')
.click();
cy.getByTestId(connectEthWalletBtn).should('exist');
});
});
+1
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
NX_VEGA_ENV=STAGNET3
+1
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=''
NX_VEGA_ENV=CUSTOM
+2
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_ENV=DEVNET
@@ -9,3 +10,4 @@ NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
+2
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_ENV=MAINNET
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
@@ -8,3 +9,4 @@ NX_VEGA_TOKEN_URL=https://token.vega.xyz
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
+2
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
NX_VEGA_ENV=MIRROR
@@ -9,3 +10,4 @@ NX_VEGA_TOKEN_URL=https://mainnet-mirror.token.vega.xyz
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
+2
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.sandbox.vega.xyz
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/sandbox/vegawallet-sandbox.toml
NX_VEGA_ENV=SANDBOX
@@ -7,3 +8,4 @@ NX_VEGA_NETWORKS={\"DEVNET\":\"https://dev.token.vega.xyz\",\"STAGNET3\":\"https
NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
+2
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1
@@ -9,3 +10,4 @@ NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
+2
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml
NX_VEGA_ENV=STAGNET3
@@ -9,3 +10,4 @@ NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
+2
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
NX_VEGA_ENV=TESTNET
@@ -9,3 +10,4 @@ NX_VEGA_TOKEN_URL=https://token.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
+2 -1
View File
@@ -1,5 +1,6 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/testnet2/testnet2.toml
NX_VEGA_ENV=VALIDATOR_TESTNET
@@ -9,4 +10,4 @@ NX_VEGA_TOKEN_URL=https://validator-testnet.governance.fairground.wtf
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
@@ -282,7 +282,7 @@ export const LiquidityViewContainer = ({
<div className="break-word">{marketId}</div>
</HeaderStat>
</Header>
<Tabs active={getActiveDefaultId()}>
<Tabs defaultValue={getActiveDefaultId()}>
<Tab
id={LiquidityTabs.MyLiquidityProvision}
name={t('My liquidity provision')}
+6 -39
View File
@@ -1,9 +1,9 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import debounce from 'lodash/debounce';
import React, { useCallback, useEffect, useMemo } from 'react';
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
useDataProvider,
useScreenDimensions,
useThrottledDataProvider,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer, ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
@@ -61,7 +61,8 @@ export const MarketPage = () => {
const { marketId } = useParams();
const navigate = useNavigate();
const { w } = useWindowSize();
const { screenSize } = useScreenDimensions();
const largeScreen = ['lg', 'xl', 'xxl', 'xxxl'].includes(screenSize);
const update = useGlobalStore((store) => store.update);
const lastMarketId = useGlobalStore((store) => store.marketId);
@@ -87,7 +88,7 @@ export const MarketPage = () => {
}, [update, lastMarketId, data?.id]);
const tradeView = useMemo(() => {
if (w > 960) {
if (largeScreen) {
return (
<TradeGrid
market={data}
@@ -105,7 +106,7 @@ export const MarketPage = () => {
onClickCollateral={() => navigate('/portfolio')}
/>
);
}, [w, data, onSelect, navigate]);
}, [largeScreen, data, onSelect, navigate]);
if (!data && marketId) {
return (
<Splash>
@@ -140,37 +141,3 @@ export const MarketPage = () => {
</AsyncRenderer>
);
};
const useWindowSize = () => {
const [windowSize, setWindowSize] = useState(() => {
if (typeof window !== 'undefined') {
return {
w: window.innerWidth,
h: window.innerHeight,
};
}
// Something sensible for server rendered page
return {
w: 1200,
h: 900,
};
});
useEffect(() => {
const handleResize = debounce(({ target }) => {
setWindowSize({
w: target.innerWidth,
h: target.innerHeight,
});
}, 300);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return windowSize;
};
+196 -120
View File
@@ -8,13 +8,13 @@ import { TradesContainer } from '@vegaprotocol/trades';
import { LayoutPriority } from 'allotment';
import classNames from 'classnames';
import AutoSizer from 'react-virtualized-auto-sizer';
import { memo, useState } from 'react';
import { memo, useCallback, useState } from 'react';
import type { ReactNode, ComponentProps } from 'react';
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import {
Tab,
Tabs,
LocalStoragePersistTabs as Tabs,
ResizableGrid,
ResizableGridPanel,
Splash,
@@ -29,6 +29,7 @@ import { LiquidityContainer } from '../liquidity/liquidity';
import { useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import type { PinnedAsset } from '@vegaprotocol/accounts';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
type MarketDependantView =
| typeof CandlesChartContainer
@@ -69,129 +70,204 @@ interface TradeGridProps {
pinnedAsset?: PinnedAsset;
}
const MainGrid = ({
marketId,
onSelect,
pinnedAsset,
}: {
interface BottomPanelProps {
marketId: string;
onSelect?: (marketId: string) => void;
pinnedAsset?: PinnedAsset;
}) => {
const navigate = useNavigate();
const onMarketClick = (marketId: string) => {
navigate(Links[Routes.MARKET](marketId), {
replace: true,
});
};
return (
<ResizableGrid vertical>
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
<ResizableGrid proportionalLayout={false} minSize={200}>
<ResizableGridPanel
priority={LayoutPriority.High}
minSize={200}
preferredSize="50%"
>
<TradeGridChild>
<Tabs>
<Tab id="chart" name={t('Chart')}>
<TradingViews.Candles marketId={marketId} />
</Tab>
<Tab id="depth" name={t('Depth')}>
<TradingViews.Depth marketId={marketId} />
</Tab>
<Tab id="liquidity" name={t('Liquidity')}>
<TradingViews.Liquidity marketId={marketId} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={330}
minSize={300}
>
<TradeGridChild>
<Tabs>
<Tab id="ticket" name={t('Ticket')}>
<TradingViews.Ticket
}
const MarketBottomPanel = memo(
({ marketId, pinnedAsset }: BottomPanelProps) => {
const { screenSize } = useScreenDimensions();
const navigate = useNavigate();
const onMarketClick = useCallback(
(marketId: string) => {
navigate(Links[Routes.MARKET](marketId), {
replace: true,
});
},
[navigate]
);
return 'xxxl' === screenSize ? (
<ResizableGrid proportionalLayout minSize={200}>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize="50%"
minSize={50}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-orders">
<Tab id="orders" name={t('Orders')}>
<VegaWalletContainer>
<TradingViews.Orders
marketId={marketId}
onClickCollateral={() => navigate('/portfolio')}
onMarketClick={onMarketClick}
/>
</Tab>
<Tab id="info" name={t('Info')}>
<TradingViews.Info
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<TradingViews.Fills
marketId={marketId}
onSelect={(id: string) => {
onSelect?.(id);
}}
onMarketClick={onMarketClick}
/>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={430}
minSize={200}
>
<TradeGridChild>
<Tabs>
<Tab id="orderbook" name={t('Orderbook')}>
<TradingViews.Orderbook marketId={marketId} />
</Tab>
<Tab id="trades" name={t('Trades')}>
<TradingViews.Trades marketId={marketId} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
</ResizableGrid>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize="25%"
minSize={50}
>
<TradeGridChild>
<Tabs>
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.Positions onMarketClick={onMarketClick} />
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('Orders')}>
<VegaWalletContainer>
<TradingViews.Orders
marketId={marketId}
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<TradingViews.Fills
marketId={marketId}
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<VegaWalletContainer>
<TradingViews.Collateral
pinnedAsset={pinnedAsset}
hideButtons
/>
</VegaWalletContainer>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
</ResizableGrid>
);
};
const MainGridWrapped = memo(MainGrid);
</VegaWalletContainer>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize="50%"
minSize={50}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-positions">
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.Positions
onMarketClick={onMarketClick}
noBottomPlaceholder
/>
</VegaWalletContainer>
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<VegaWalletContainer>
<TradingViews.Collateral
pinnedAsset={pinnedAsset}
noBottomPlaceholder
hideButtons
/>
</VegaWalletContainer>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
</ResizableGrid>
) : (
<TradeGridChild>
<Tabs storageKey="console-trade-grid-positions">
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.Positions onMarketClick={onMarketClick} />
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('Orders')}>
<VegaWalletContainer>
<TradingViews.Orders
marketId={marketId}
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<TradingViews.Fills
marketId={marketId}
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<VegaWalletContainer>
<TradingViews.Collateral pinnedAsset={pinnedAsset} hideButtons />
</VegaWalletContainer>
</Tab>
</Tabs>
</TradeGridChild>
);
}
);
MarketBottomPanel.displayName = 'MarketBottomPanel';
const MainGrid = memo(
({
marketId,
onSelect,
pinnedAsset,
}: {
marketId: string;
onSelect?: (marketId: string) => void;
pinnedAsset?: PinnedAsset;
}) => {
const navigate = useNavigate();
return (
<ResizableGrid vertical>
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
<ResizableGrid proportionalLayout={false} minSize={200}>
<ResizableGridPanel
priority={LayoutPriority.High}
minSize={200}
preferredSize="50%"
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-chart">
<Tab id="chart" name={t('Chart')}>
<TradingViews.Candles marketId={marketId} />
</Tab>
<Tab id="depth" name={t('Depth')}>
<TradingViews.Depth marketId={marketId} />
</Tab>
<Tab id="liquidity" name={t('Liquidity')}>
<TradingViews.Liquidity marketId={marketId} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={330}
minSize={300}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-ticket">
<Tab id="ticket" name={t('Ticket')}>
<TradingViews.Ticket
marketId={marketId}
onClickCollateral={() => navigate('/portfolio')}
/>
</Tab>
<Tab id="info" name={t('Info')}>
<TradingViews.Info
marketId={marketId}
onSelect={(id: string) => {
onSelect?.(id);
}}
/>
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize={430}
minSize={200}
>
<TradeGridChild>
<Tabs storageKey="console-trade-grid-orderbook">
<Tab id="orderbook" name={t('Orderbook')}>
<TradingViews.Orderbook marketId={marketId} />
</Tab>
<Tab id="trades" name={t('Trades')}>
<TradingViews.Trades marketId={marketId} />
</Tab>
</Tabs>
</TradeGridChild>
</ResizableGridPanel>
</ResizableGrid>
</ResizableGridPanel>
<ResizableGridPanel
priority={LayoutPriority.Low}
preferredSize="25%"
minSize={50}
>
<MarketBottomPanel marketId={marketId} pinnedAsset={pinnedAsset} />
</ResizableGridPanel>
</ResizableGrid>
);
}
);
MainGrid.displayName = 'MainGrid';
export const TradeGrid = ({
market,
@@ -201,7 +277,7 @@ export const TradeGrid = ({
return (
<div className="h-full grid grid-rows-[min-content_1fr]">
<TradeMarketHeader market={market} onSelect={onSelect} />
<MainGridWrapped
<MainGrid
marketId={market?.id || ''}
onSelect={onSelect}
pinnedAsset={pinnedAsset}
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { titlefy } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { Tabs, Tab } from '@vegaprotocol/ui-toolkit';
import { LocalStoragePersistTabs as Tabs, Tab } from '@vegaprotocol/ui-toolkit';
import { Markets } from './markets';
import { Proposed } from './proposed';
import { usePageTitleStore } from '../../stores';
@@ -14,7 +14,7 @@ export const MarketsPage = () => {
updateTitle(titlefy(['Markets']));
}, [updateTitle]);
return (
<Tabs>
<Tabs storageKey="console-markets">
<Tab id="all-markets" name={t('All markets')}>
<Markets />
</Tab>
@@ -2,7 +2,11 @@ import { titlefy } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { PositionsContainer } from '@vegaprotocol/positions';
import { OrderListContainer } from '@vegaprotocol/orders';
import { ResizableGridPanel, Tab, Tabs } from '@vegaprotocol/ui-toolkit';
import {
ResizableGridPanel,
Tab,
LocalStoragePersistTabs as Tabs,
} from '@vegaprotocol/ui-toolkit';
import { WithdrawalsContainer } from './withdrawals-container';
import { FillsContainer } from '@vegaprotocol/fills';
import type { ReactNode } from 'react';
@@ -41,7 +45,7 @@ export const Portfolio = () => {
<ResizableGrid vertical>
<ResizableGridPanel minSize={75}>
<PortfolioGridChild>
<Tabs>
<Tabs storageKey="console-portfolio-account-history">
<Tab id="account-history" name={t('Account history')}>
<VegaWalletContainer>
<AccountHistoryContainer />
@@ -79,7 +83,7 @@ export const Portfolio = () => {
minSize={50}
>
<PortfolioGridChild>
<Tabs>
<Tabs storageKey="console-portfolio-collateral">
<Tab id="collateral" name={t('Collateral')}>
<VegaWalletContainer>
<AccountsContainer />
@@ -12,9 +12,11 @@ import { useDepositDialog } from '@vegaprotocol/deposits';
export const AccountsContainer = ({
pinnedAsset,
hideButtons,
noBottomPlaceholder,
}: {
pinnedAsset?: PinnedAsset;
hideButtons?: boolean;
noBottomPlaceholder?: boolean;
}) => {
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
@@ -46,6 +48,7 @@ export const AccountsContainer = ({
onClickDeposit={openDepositDialog}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
noBottomPlaceholder={noBottomPlaceholder}
/>
{!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
+11 -3
View File
@@ -6,9 +6,10 @@ import { Intent } from '@vegaprotocol/ui-toolkit';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
useEnvironment: jest
.fn()
.mockImplementation(() => ({ VEGA_URL: 'https://vega-url.wtf' })),
useEnvironment: jest.fn().mockImplementation(() => ({
VEGA_URL: 'https://vega-url.wtf',
VEGA_INCIDENT_URL: 'https://blog.vega.community',
})),
}));
const mockSetNodeSwitcher = jest.fn();
@@ -26,6 +27,13 @@ describe('NodeHealth', () => {
await userEvent.click(screen.getByRole('button'));
expect(mockSetNodeSwitcher).toHaveBeenCalled();
});
it('External link to blog should be present', () => {
render(<NodeHealth />, { wrapper: MockedProvider });
expect(
screen.getByRole('link', { name: /^Mainnet status & incidents/ })
).toBeInTheDocument();
});
});
describe('NodeUrl', () => {
+40 -16
View File
@@ -2,7 +2,7 @@ import { useCallback } from 'react';
import { useEnvironment, useNodeHealth } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import type { Intent } from '@vegaprotocol/ui-toolkit';
import { Indicator } from '@vegaprotocol/ui-toolkit';
import { Indicator, ExternalLink } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import type { ButtonHTMLAttributes, ReactNode } from 'react';
import { useGlobalStore } from '../../stores';
@@ -19,7 +19,7 @@ export const Footer = () => {
};
export const NodeHealth = () => {
const { VEGA_URL } = useEnvironment();
const { VEGA_URL, VEGA_INCIDENT_URL } = useEnvironment();
const setNodeSwitcher = useGlobalStore(
(store) => (open: boolean) => store.update({ nodeSwitcherDialog: open })
);
@@ -27,19 +27,36 @@ export const NodeHealth = () => {
const onClick = useCallback(() => {
setNodeSwitcher(true);
}, [setNodeSwitcher]);
return VEGA_URL ? (
<FooterButton onClick={onClick} data-testid="node-health">
<FooterButtonPart>
<HealthIndicator text={text} intent={intent} />
</FooterButtonPart>
<FooterButtonPart>
<NodeUrl url={VEGA_URL} />
</FooterButtonPart>
<FooterButtonPart>
<span title={t('Block height')}>{datanodeBlockHeight}</span>
</FooterButtonPart>
</FooterButton>
) : null;
const incidentsLink = VEGA_INCIDENT_URL && (
<ExternalLink className="ml-1" href={VEGA_INCIDENT_URL}>
{t('Mainnet status & incidents')}
</ExternalLink>
);
return (
<>
{VEGA_URL && (
<FooterButton onClick={onClick} data-testid="node-health">
<FooterButtonPart>
<HealthIndicator text={text} intent={intent} />
</FooterButtonPart>
<FooterButtonPart>
<NodeUrl url={VEGA_URL} />
</FooterButtonPart>
{/* create a monospace effect - avoiding jumps of width */}
<FooterButtonPart
width={`${
datanodeBlockHeight
? String(datanodeBlockHeight).length + 'ch'
: 'auto'
}`}
>
<span title={t('Block height')}>{datanodeBlockHeight}</span>
</FooterButtonPart>
</FooterButton>
)}
{incidentsLink}
</>
);
};
interface NodeUrlProps {
@@ -78,9 +95,16 @@ const FooterButton = (props: FooterButtonProps) => {
return <button {...props} className={buttonClasses} />;
};
const FooterButtonPart = ({ children }: { children: ReactNode }) => {
const FooterButtonPart = ({
width = 'auto',
children,
}: {
children: ReactNode;
width?: string;
}) => {
return (
<span
style={{ width }}
className={classNames(
'relative inline-block mr-2 last:mr-0 pr-2 last:pr-0',
'last:after:hidden',
@@ -93,7 +93,8 @@ export const MarketLiquiditySupplied = ({
percentage.gte(100) &&
market?.marketTradingMode ===
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
market.trigger === AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY;
market.trigger ===
AuctionTrigger.AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS;
const description = marketId ? (
<section>
+44 -7
View File
@@ -1,8 +1,9 @@
import type { ComponentProps } from 'react';
import type { ComponentProps, ReactNode } from 'react';
import {
DApp,
NetworkSwitcher,
TOKEN_GOVERNANCE,
useEnvironment,
useLinks,
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
@@ -17,6 +18,8 @@ import {
ExternalLink,
Icon,
NavigationBreakpoint,
NavigationTrigger,
NavigationContent,
} from '@vegaprotocol/ui-toolkit';
import { Links, Routes } from '../../pages/client-router';
@@ -26,6 +29,7 @@ export const Navbar = ({
}: {
theme: ComponentProps<typeof Navigation>['theme'];
}) => {
const { VEGA_DOCS_URL, GITHUB_FEEDBACK_URL } = useEnvironment();
const tokenLink = useLinks(DApp.Token);
const { marketId } = useGlobalStore((store) => ({
marketId: store.marketId,
@@ -75,13 +79,29 @@ export const Navbar = ({
</NavigationLink>
</NavigationItem>
<NavigationItem>
<ExternalLink href={tokenLink(TOKEN_GOVERNANCE)}>
<span className="flex items-center gap-2">
<span>{t('Governance')}</span>{' '}
<Icon name="arrow-top-right" size={3} />
</span>
</ExternalLink>
<NavExternalLink href={tokenLink(TOKEN_GOVERNANCE)}>
{t('Governance')}
</NavExternalLink>
</NavigationItem>
{VEGA_DOCS_URL && GITHUB_FEEDBACK_URL && (
<NavigationItem>
<NavigationTrigger>{t('Resources')}</NavigationTrigger>
<NavigationContent>
<NavigationList>
<NavigationItem>
<NavExternalLink href={VEGA_DOCS_URL}>
{t('Docs')}
</NavExternalLink>
</NavigationItem>
<NavigationItem>
<NavExternalLink href={GITHUB_FEEDBACK_URL}>
{t('Give Feedback')}
</NavExternalLink>
</NavigationItem>
</NavigationList>
</NavigationContent>
</NavigationItem>
)}
</NavigationList>
<NavigationList
className="[.drawer-content_&]:border-t [.drawer-content_&]:border-t-vega-light-200 dark:[.drawer-content_&]:border-t-vega-dark-200 [.drawer-content_&]:pt-8 [.drawer-content_&]:mt-4"
@@ -98,3 +118,20 @@ export const Navbar = ({
</Navigation>
);
};
const NavExternalLink = ({
children,
href,
}: {
children: ReactNode;
href: string;
}) => {
return (
<ExternalLink href={href}>
<span className="flex items-center gap-2">
<span>{children}</span>
<Icon name="arrow-top-right" size={3} />
</span>
</ExternalLink>
);
};
@@ -19,6 +19,7 @@ interface AccountManagerProps {
onClickDeposit?: (assetId?: string) => void;
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
noBottomPlaceholder?: boolean;
}
export const AccountManager = ({
@@ -28,6 +29,7 @@ export const AccountManager = ({
partyId,
isReadOnly,
pinnedAsset,
noBottomPlaceholder,
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
@@ -45,6 +47,7 @@ export const AccountManager = ({
const bottomPlaceholderProps = useBottomPlaceholder<AccountFields>({
gridRef,
setId,
disabled: noBottomPlaceholder,
});
const getRowHeight = useCallback(
+4 -1
View File
@@ -46,7 +46,10 @@ addVegaWalletSubmitProposal();
addVegaWalletSubmitLiquidityProvision();
addImportNodeWallets();
export { mockConnectWallet } from './lib/commands/vega-wallet-connect';
export {
mockConnectWallet,
mockConnectWalletWithUserError,
} from './lib/commands/vega-wallet-connect';
export type { onMessage } from './lib/mock-ws';
export { aliasGQLQuery } from './lib/mock-gql';
export { aliasWalletQuery } from './lib/mock-rest';
@@ -35,6 +35,8 @@ function createNewMarketProposal(): ProposalSubmissionBody {
changes: {
decimalPlaces: '5',
positionDecimalPlaces: '5',
linearSlippageFactor: '0.001',
quadraticSlippageFactor: '0',
lpPriceRange: '10',
instrument: {
name: 'Test market 1',
@@ -1,4 +1,7 @@
import { aliasWalletConnectQuery } from '../mock-rest';
import {
aliasWalletConnectQuery,
aliasWalletConnectWithUserError,
} from '../mock-rest';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
@@ -20,6 +23,12 @@ export const mockConnectWallet = () => {
});
};
export const mockConnectWalletWithUserError = () => {
cy.mockWallet((req) => {
aliasWalletConnectWithUserError(req);
});
};
export function addVegaWalletConnect() {
Cypress.Commands.add('connectVegaWallet', (isMobile) => {
mockConnectWallet();
+20
View File
@@ -63,3 +63,23 @@ export const aliasWalletConnectQuery = (
});
}
};
export const aliasWalletConnectWithUserError = (
req: CyHttpMessages.IncomingHttpRequest
) => {
if (hasMethod(req, 'client.connect_wallet')) {
req.alias = 'client.connect_wallet';
req.reply({
statusCode: 400,
body: {
jsonrpc: '2.0',
error: {
code: 3001,
data: 'the user rejected the wallet connection',
message: 'User error',
},
id: '0',
},
});
}
};
@@ -30,9 +30,12 @@ export const compileGridData = (
): { label: ReactNode; value?: ReactNode }[] => {
const grid: SimpleGridProps['grid'] = [];
const isLiquidityMonitoringAuction =
marketData?.marketTradingMode ===
(marketData?.marketTradingMode ===
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
marketData?.trigger === Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY;
marketData?.trigger ===
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET) ||
marketData?.trigger ===
Schema.AuctionTrigger.AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS;
const formatStake = (value: string) => {
const formattedValue = addDecimalsFormatNumber(
@@ -9,7 +9,6 @@ import * as Schema from '@vegaprotocol/types';
import { ExternalLink, SimpleGrid } from '@vegaprotocol/ui-toolkit';
import { compileGridData } from './compile-grid-data';
import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list';
import BigNumber from 'bignumber.js';
type TradingModeTooltipProps = {
marketId?: string;
@@ -115,23 +114,39 @@ export const TradingModeTooltip = ({
}
case Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION: {
switch (trigger) {
case Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY: {
const notEnoughLiquidity = new BigNumber(
marketData.suppliedStake || 0
).isLessThan(marketData.targetStake || 0);
case Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET: {
return (
<section data-testid="trading-mode-tooltip">
<p className={classNames({ 'mb-4': Boolean(compiledGrid) })}>
<span className="mb-2">
{notEnoughLiquidity &&
t(
'This market is in auction until it reaches sufficient liquidity.'
)}
{!notEnoughLiquidity &&
t(
'This market may have sufficient liquidity but there are not enough priced limit orders in the order book, which are required to deploy liquidity commitment pegged orders.'
)}
</span>{' '}
{t(
'This market is in auction until it reaches sufficient liquidity.'
)}
</span>
{VEGA_DOCS_URL && (
<ExternalLink
href={
createDocsLinks(VEGA_DOCS_URL)
.AUCTION_TYPE_LIQUIDITY_MONITORING
}
>
{t('Find out more')}
</ExternalLink>
)}
</p>
{compiledGrid && <SimpleGrid grid={compiledGrid} />}
</section>
);
}
case Schema.AuctionTrigger.AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS: {
return (
<section data-testid="trading-mode-tooltip">
<p className={classNames({ 'mb-4': Boolean(compiledGrid) })}>
<span className="mb-2">
{t(
'This market may have sufficient liquidity but there are not enough priced limit orders in the order book, which are required to deploy liquidity commitment pegged orders.'
)}
</span>
{VEGA_DOCS_URL && (
<ExternalLink
href={
@@ -13,7 +13,10 @@ export const validateTimeInForce = (
const isPriceTrigger =
trigger === Schema.AuctionTrigger.AUCTION_TRIGGER_PRICE;
const isLiquidityTrigger =
trigger === Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY;
trigger ===
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET ||
trigger ===
Schema.AuctionTrigger.AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS;
if (isMarketInAuction(marketTradingMode)) {
if (
+4 -1
View File
@@ -17,7 +17,10 @@ export const validateType = (
const isPriceTrigger =
trigger === Schema.AuctionTrigger.AUCTION_TRIGGER_PRICE;
const isLiquidityTrigger =
trigger === Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY;
trigger ===
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET ||
trigger ===
Schema.AuctionTrigger.AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS;
if (isMonitoringAuction && isPriceTrigger) {
return MarketModeValidationType.PriceMonitoringAuction;
@@ -285,6 +285,7 @@ function compileEnvVars() {
GIT_BRANCH: process.env['GIT_COMMIT_BRANCH'],
GIT_COMMIT_HASH: process.env['GIT_COMMIT_HASH'],
GIT_ORIGIN_URL: process.env['GIT_ORIGIN_URL'],
VEGA_INCIDENT_URL: process.env['NX_VEGA_INCIDENT_URL'],
};
return env;
@@ -50,6 +50,7 @@ const schemaObject = {
MAINTENANCE_PAGE: z.optional(z.boolean()),
ETH_LOCAL_PROVIDER_URL: z.optional(z.string()),
ETH_WALLET_MNEMONIC: z.optional(z.string()),
VEGA_INCIDENT_URL: z.optional(z.string()),
};
// combine schema above with custom rule to ensure either
@@ -74,7 +74,7 @@ describe('updateLevels', () => {
const updates = [{ price: '132', volume: '200' }];
expect(updateLevels(priceLevels, updates, 2, 0, true)).toEqual([
expect(updateLevels(priceLevels, updates, 2, 0, false)).toEqual([
{ price: 1.35, volume: 200 },
{ price: 1.32, volume: 200 },
{ price: 1.28, volume: 100 },
@@ -1,4 +1,6 @@
import { addDecimal } from '@vegaprotocol/utils';
import uniqBy from 'lodash/uniqBy';
import reverse from 'lodash/reverse';
interface PriceLevel {
price: number;
@@ -41,9 +43,9 @@ export const updateLevels = (
updates: RawPriceLevel[],
decimalPlaces: number,
positionDecimalPlaces: number,
reverse = false
ascending = true
) => {
updates.forEach((update) => {
uniqBy(reverse(updates || []), 'price').forEach((update) => {
const updateLevel = parseLevel(
update,
decimalPlaces,
@@ -63,9 +65,9 @@ export const updateLevels = (
}
} else if (update.volume !== '0') {
index = levels.findIndex((level) =>
reverse
? level.price < updateLevel.price
: level.price > updateLevel.price
ascending
? level.price > updateLevel.price
: level.price < updateLevel.price
);
if (index !== -1) {
levels.splice(index, 0, updateLevel);
+49 -17
View File
@@ -9,6 +9,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import type { MarketData } from '@vegaprotocol/market-list';
import type {
MarketDepthQuery,
MarketDepthUpdateSubscription,
PriceLevelFieldsFragment,
} from './__generated__/MarketDepth';
@@ -22,6 +23,18 @@ interface DepthChartManagerProps {
const formatMidPrice = (midPrice: string, decimalPlaces: number) =>
Number(addDecimal(midPrice, decimalPlaces));
const getMidPrice = (
sell: PriceLevelFieldsFragment[] | null | undefined,
buy: PriceLevelFieldsFragment[] | null | undefined,
decimalPlaces: number
) =>
buy?.length && sell?.length
? formatMidPrice(
((BigInt(buy[0].price) + BigInt(sell[0].price)) / BigInt(2)).toString(),
decimalPlaces
)
: undefined;
type DepthData = Pick<DepthChartProps, 'data' | 'midPrice'>;
export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
@@ -30,6 +43,7 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
const [depthData, setDepthData] = useState<DepthData | null>(null);
const dataRef = useRef<DepthData | null>(null);
const marketDataRef = useRef<MarketData | null>(null);
const rawDataRef = useRef<MarketDepthQuery['market'] | null>(null);
const deltaRef = useRef<{
sell: PriceLevelFieldsFragment[];
buy: PriceLevelFieldsFragment[];
@@ -56,28 +70,28 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
}
dataRef.current = {
...dataRef.current,
midPrice: marketDataRef.current?.staticMidPrice
? formatMidPrice(
marketDataRef.current?.staticMidPrice,
market.decimalPlaces
)
: undefined,
midPrice: getMidPrice(
rawDataRef.current?.depth.sell,
rawDataRef.current?.depth.buy,
market.decimalPlaces
),
data: {
buy: deltaRef.current.buy
buy: deltaRef.current.buy.length
? updateLevels(
dataRef.current.data.buy,
deltaRef.current.buy,
market.decimalPlaces,
market.positionDecimalPlaces,
true
false
)
: dataRef.current.data.buy,
sell: deltaRef.current.sell
sell: deltaRef.current.sell.length
? updateLevels(
dataRef.current.data.sell,
deltaRef.current.sell,
market.decimalPlaces,
market.positionDecimalPlaces
market.positionDecimalPlaces,
true
)
: dataRef.current.data.sell,
},
@@ -85,16 +99,23 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
deltaRef.current.buy = [];
deltaRef.current.sell = [];
setDepthData(dataRef.current);
}, 1000),
}, 250),
[market]
);
useEffect(() => {
deltaRef.current.buy = [];
deltaRef.current.sell = [];
}, [marketId]);
// Apply updates to the table
const update = useCallback(
({
delta: deltas,
data: rawData,
}: {
delta?: MarketDepthUpdateSubscription['marketsDepthUpdate'];
delta?: MarketDepthUpdateSubscription['marketsDepthUpdate'] | null;
data: NonNullable<MarketDepthQuery['market']> | null;
}) => {
if (!dataRef.current) {
return false;
@@ -109,6 +130,7 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
if (delta.buy) {
deltaRef.current.buy.push(...delta.buy);
}
rawDataRef.current = rawData;
updateDepthData();
}
return true;
@@ -116,7 +138,11 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
[marketId, updateDepthData]
);
const { data, error, loading } = useDataProvider({
const { data, error, loading } = useDataProvider<
NonNullable<MarketDepthQuery['market']> | null,
MarketDepthUpdateSubscription['marketsDepthUpdate'],
{ marketId: string }
>({
dataProvider: marketDepthProvider,
update,
variables,
@@ -155,9 +181,11 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
return;
}
dataRef.current = {
midPrice: marketData.staticMidPrice
? formatMidPrice(marketData.staticMidPrice, market.decimalPlaces)
: undefined,
midPrice: getMidPrice(
data.depth.sell,
data.depth.buy,
market.decimalPlaces
),
data: {
buy:
data.depth.buy?.map((priceLevel) =>
@@ -177,8 +205,12 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
) ?? [],
},
};
rawDataRef.current = data;
setDepthData(dataRef.current);
}, [data, marketData, market]);
return () => {
updateDepthData.cancel();
};
}, [data, marketData, market, updateDepthData]);
const volumeFormat = useCallback(
(volume: number) =>
@@ -75,6 +75,11 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
}, 250)
);
useEffect(() => {
deltaRef.current.buy = [];
deltaRef.current.sell = [];
}, [marketId]);
const update = useCallback(
({
delta: deltas,
@@ -74,7 +74,7 @@ const marketsDataFieldsFragments: MarketsDataFieldsFragment[] = [
bestBidPrice: '0',
bestOfferPrice: '0',
markPrice: '4612690058',
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY,
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET,
__typename: 'MarketData',
},
{
@@ -91,7 +91,7 @@ const marketsDataFieldsFragments: MarketsDataFieldsFragment[] = [
bestBidPrice: '0',
bestOfferPrice: '0',
markPrice: '4612690058',
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY,
trigger: Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET,
__typename: 'MarketData',
},
];
+2 -2
View File
@@ -5,9 +5,9 @@ import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type PositionFieldsFragment = { __typename?: 'Position', realisedPNL: string, openVolume: string, unrealisedPNL: string, averageEntryPrice: string, updatedAt?: any | null, positionStatus: Types.PositionStatus, lossSocializationAmount: string, market: { __typename?: 'Market', id: string } };
export type PositionsQueryVariables = {
export type PositionsQueryVariables = Types.Exact<{
partyId: Types.Scalars['ID'];
};
}>;
export type PositionsQuery = { __typename?: 'Query', party?: { __typename?: 'Party', id: string, positionsConnection?: { __typename?: 'PositionConnection', edges?: Array<{ __typename?: 'PositionEdge', node: { __typename?: 'Position', realisedPNL: string, openVolume: string, unrealisedPNL: string, averageEntryPrice: string, updatedAt?: any | null, positionStatus: Types.PositionStatus, lossSocializationAmount: string, market: { __typename?: 'Market', id: string } } }> | null } | null } | null };
+3 -2
View File
@@ -68,10 +68,11 @@ export const PositionsManager = ({
const bottomPlaceholderProps = useBottomPlaceholder<Position>({
gridRef,
setId,
disabled: noBottomPlaceholder,
});
useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [data]);
}, [data?.length]);
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []);
@@ -86,7 +87,7 @@ export const PositionsManager = ({
suppressNoRowsOverlay
isReadOnly={isReadOnly}
onFilterChanged={onFilterChanged}
{...(noBottomPlaceholder ? null : bottomPlaceholderProps)}
{...bottomPlaceholderProps}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
+16 -1
View File
@@ -116,6 +116,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
value
)
}
minWidth={190}
/>
<AgGridColumn
headerName={t('Notional')}
@@ -141,6 +142,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.marketDecimalPlaces
);
}}
minWidth={80}
/>
<AgGridColumn
headerName={t('Open volume')}
@@ -174,6 +176,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
);
}}
cellRenderer={OpenVolumeCell}
minWidth={100}
/>
<AgGridColumn
headerName={t('Mark price')}
@@ -213,8 +216,13 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.marketDecimalPlaces
);
}}
minWidth={100}
/>
<AgGridColumn
headerName={t('Settlement asset')}
field="assetSymbol"
minWidth={100}
/>
<AgGridColumn headerName={t('Settlement asset')} field="assetSymbol" />
<AgGridColumn
headerName={t('Entry price')}
field="averageEntryPrice"
@@ -248,6 +256,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.marketDecimalPlaces
);
}}
minWidth={100}
/>
<AgGridColumn
headerName={t('Leverage')}
@@ -264,6 +273,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
}: VegaValueFormatterParams<Position, 'currentLeverage'>) =>
value === undefined ? undefined : formatNumber(value.toString(), 1)
}
minWidth={100}
/>
<AgGridColumn
headerName={t('Margin allocated')}
@@ -295,6 +305,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
data.decimals
);
}}
minWidth={100}
/>
<AgGridColumn
headerName={t('Realised PNL')}
@@ -321,6 +332,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
'Profit or loss is realised whenever your position is reduced to zero and the margin is released back to your collateral balance. P&L excludes any fees paid.'
)}
cellRenderer={PNLCell}
minWidth={100}
/>
<AgGridColumn
headerName={t('Unrealised PNL')}
@@ -347,6 +359,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
'Unrealised profit is the current profit on your open position. Margin is still allocated to your position.'
)}
cellRenderer={PNLCell}
minWidth={100}
/>
<AgGridColumn
headerName={t('Updated')}
@@ -361,6 +374,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
}
return getDateTimeFormat().format(new Date(value));
}}
minWidth={150}
/>
{onClose && !props.isReadOnly ? (
<AgGridColumn
@@ -375,6 +389,7 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
</ButtonLink>
) : null
}
minWidth={80}
/>
) : null}
</AgGrid>
@@ -11,11 +11,13 @@ const isFullWidthRow = (params: IsFullWidthRowParams) =>
interface Props<T> {
gridRef: RefObject<AgGridReact>;
setId?: (data: T) => T;
disabled?: boolean;
}
// eslint-disable-next-line @typescript-eslint/ban-types
export const useBottomPlaceholder = <T extends {}>({
gridRef,
setId,
disabled,
}: Props<T>) => {
const onBodyScrollEnd = useCallback(() => {
const rowCont = gridRef.current?.api.getModel().getRowCount() ?? 0;
@@ -58,14 +60,17 @@ export const useBottomPlaceholder = <T extends {}>({
}, [gridRef, onBodyScrollEnd]);
return useMemo(
() => ({
onBodyScrollEnd,
rowClassRules: NO_HOVER_CSS_RULE,
isFullWidthRow,
fullWidthCellRenderer,
onSortChanged: onRowsChanged,
onFilterChange: onRowsChanged,
}),
[onBodyScrollEnd, onRowsChanged]
() =>
!disabled
? {
onBodyScrollEnd,
rowClassRules: NO_HOVER_CSS_RULE,
isFullWidthRow,
fullWidthCellRenderer,
onSortChanged: onRowsChanged,
onFilterChange: onRowsChanged,
}
: {},
[onBodyScrollEnd, onRowsChanged, disabled]
);
};
@@ -1,5 +1,5 @@
import { act, renderHook } from '@testing-library/react';
import { useLocalStorage } from './use-local-storage';
import { useLocalStorage, useLocalStorageSnapshot } from './use-local-storage';
describe('useLocalStorage', () => {
afterEach(() => window.localStorage.clear());
@@ -50,3 +50,51 @@ describe('useLocalStorage', () => {
expect(B.current[0]).toBeNull();
});
});
describe('useLocalStorageSnapshot', () => {
afterEach(() => window.localStorage.clear());
it("should return null if there's no value set", () => {
const { result } = renderHook(() => useLocalStorageSnapshot('test'));
const [value] = result.current;
expect(value).toBeNull();
});
it('should return already saved value', () => {
window.localStorage.setItem('test', '123');
const { result } = renderHook(() => useLocalStorageSnapshot('test'));
const [value] = result.current;
expect(value).toEqual('123');
});
it('should save given value', () => {
const { result } = renderHook(() => useLocalStorageSnapshot('test'));
const setValue = result.current[1];
expect(result.current[0]).toBeNull();
act(() => setValue('123'));
expect(result.current[0]).toEqual('123');
act(() => setValue('456'));
expect(result.current[0]).toEqual('456');
});
it('should remove given value', () => {
const { result } = renderHook(() => useLocalStorageSnapshot('test'));
const setValue = result.current[1];
const removeValue = result.current[2];
act(() => setValue('123'));
expect(result.current[0]).toEqual('123');
act(() => removeValue());
expect(result.current[0]).toBeNull();
});
it('should not return value set by storage event (by another tab)', () => {
const { result: A } = renderHook(() => useLocalStorageSnapshot('test-a'));
act(() => {
window.localStorage.setItem('test-a', '123');
window.dispatchEvent(
new StorageEvent('storage', {
key: 'test-a',
oldValue: window.localStorage.getItem('test-a'),
storageArea: window.localStorage,
newValue: '123',
})
);
});
expect(A.current[0]).toBeNull();
});
});
@@ -1,4 +1,10 @@
import { useCallback, useEffect, useMemo, useSyncExternalStore } from 'react';
import {
useCallback,
useEffect,
useMemo,
useSyncExternalStore,
useState,
} from 'react';
import { LocalStorage } from '@vegaprotocol/utils';
type LocalStorageCallback = (key: string) => void;
@@ -10,6 +16,12 @@ const unregisterCallback = (callback: LocalStorageCallback) =>
const triggerCallbacks = (key: string) =>
LOCAL_STORAGE_CALLBACKS.forEach((cb) => cb(key));
type UseLocalStorageHook = [
string | null | undefined,
(value: string) => void,
() => void
];
export const useLocalStorage = (key: string) => {
const subscribe = useCallback(
(onStoreChange: () => void) => {
@@ -52,7 +64,27 @@ export const useLocalStorage = (key: string) => {
return () => window.removeEventListener('storage', onStorage);
}, [key, onStorage]);
return useMemo<
[string | null | undefined, (value: string) => void, () => void]
>(() => [value, setValue, removeValue], [removeValue, setValue, value]);
return useMemo<UseLocalStorageHook>(
() => [value, setValue, removeValue],
[removeValue, setValue, value]
);
};
export const useLocalStorageSnapshot = (key: string) => {
const [value, setStoredValue] = useState(LocalStorage.getItem(key));
const setValue = useCallback(
(value: string) => {
LocalStorage.setItem(key, value);
setStoredValue(value);
},
[key]
);
const removeValue = useCallback(() => {
LocalStorage.removeItem(key);
setStoredValue(null);
}, [key]);
return useMemo<UseLocalStorageHook>(
() => [value, setValue, removeValue],
[removeValue, setValue, value]
);
};
+13 -4
View File
@@ -1,10 +1,19 @@
import { useRef, useEffect, useState } from 'react';
const SERVER_SIDE_DIMENSIONS = {
width: 1200,
height: 900,
};
export const useResize = () => {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
const [windowSize, setWindowSize] = useState(
typeof window !== undefined
? {
width: window.innerWidth,
height: window.innerHeight,
}
: { ...SERVER_SIDE_DIMENSIONS }
);
const timeout = useRef(0);
@@ -1,29 +1,31 @@
import { useMemo } from 'react';
// @ts-ignore avoid adding declaration file
import { theme } from '@vegaprotocol/tailwindcss-config';
import { useResize } from './use-resize';
type Screen = keyof typeof theme.screens;
export type Screen = keyof typeof theme.screens;
interface Props {
isMobile: boolean;
screenSize: Screen;
width: number;
}
export const useScreenDimensions = (): Props => {
const { width } = useResize();
const isMobile = width < parseInt(theme.screens.md);
const screenSize = Object.entries(theme.screens).reduce(
(agg: Screen, entry) => {
if (width > parseInt(entry[1])) {
agg = entry[0] as Screen;
}
return agg;
},
'xs'
);
return useMemo(
() => ({
width,
isMobile: width < parseInt(theme.screens.md),
screenSize: Object.entries(theme.screens).reduce((agg: Screen, entry) => {
if (width > parseInt(entry[1])) {
agg = entry[0] as Screen;
}
return agg;
}, 'xs'),
isMobile,
screenSize,
}),
[width]
[isMobile, screenSize]
);
};
+1
View File
@@ -6,6 +6,7 @@ module.exports = {
lg: '960px',
xl: '1280px',
xxl: '1536px',
xxxl: '1800px',
},
colors: {
transparent: 'transparent',
+1 -1
View File
@@ -50,7 +50,7 @@ interface Props extends AgGridReactProps {
export const TradesTable = forwardRef<AgGridReact, Props>((props, ref) => {
return (
<AgGrid
style={{ width: '100%', height: '100%', background: 'red' }}
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No trades')}
getRowId={({ data }) => data.id}
ref={ref}
+4 -2
View File
@@ -313,12 +313,14 @@ export type AuctionEvent = {
export enum AuctionTrigger {
/** Auction because market has a frequent batch auction trading mode */
AUCTION_TRIGGER_BATCH = 'AUCTION_TRIGGER_BATCH',
/** Liquidity monitoring */
AUCTION_TRIGGER_LIQUIDITY = 'AUCTION_TRIGGER_LIQUIDITY',
/** Liquidity monitoring due to unmet target stake */
AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET = 'AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET',
/** Opening auction */
AUCTION_TRIGGER_OPENING = 'AUCTION_TRIGGER_OPENING',
/** Price monitoring */
AUCTION_TRIGGER_PRICE = 'AUCTION_TRIGGER_PRICE',
/** Liquidity monitoring due to not being able to deploy LP orders because there's nothing to peg on one or both sides of the book */
AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS = 'AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS',
/** Invalid trigger (or no auction) */
AUCTION_TRIGGER_UNSPECIFIED = 'AUCTION_TRIGGER_UNSPECIFIED'
}
+3 -1
View File
@@ -63,7 +63,9 @@ export const AuctionTriggerMapping: {
[T in AuctionTrigger]: string;
} = {
AUCTION_TRIGGER_BATCH: 'batch',
AUCTION_TRIGGER_LIQUIDITY: 'liquidity',
AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET: 'liquidity (target not met)',
AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS:
'liquidity (unable to deploy liquidity provision orders)',
AUCTION_TRIGGER_OPENING: 'opening',
AUCTION_TRIGGER_PRICE: 'price',
AUCTION_TRIGGER_UNSPECIFIED: 'unspecified',
+1 -1
View File
@@ -1,4 +1,4 @@
{
"name": "@vegaprotocol/ui-toolkit",
"version": "0.7.0"
"version": "0.8.0"
}
@@ -4,8 +4,6 @@ import type {
ReactNode,
} from 'react';
import { forwardRef } from 'react';
import type { IconName } from '../icon';
import { Icon } from '../icon';
import classnames from 'classnames';
export type ButtonVariant = 'default' | 'primary' | 'secondary' | 'ternary';
@@ -76,8 +74,8 @@ interface CommonProps {
disabled?: boolean;
fill?: boolean;
size?: ButtonSize;
icon?: IconName;
rightIcon?: IconName;
icon?: ReactNode;
rightIcon?: ReactNode;
}
export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
@@ -151,17 +149,13 @@ export const ButtonLink = forwardRef<HTMLButtonElement, ButtonLinkProps>(
interface ButtonContentProps {
children: ReactNode;
icon?: IconName;
rightIcon?: IconName;
icon?: ReactNode;
rightIcon?: ReactNode;
}
const ButtonContent = ({ children, icon, rightIcon }: ButtonContentProps) => {
const iconEl = icon ? (
<Icon name={icon} className="fill-current mr-2 align-text-top" />
) : null;
const rightIconEl = rightIcon ? (
<Icon name={rightIcon} className="fill-current ml-2 align-text-top" />
) : null;
const iconEl = icon ? icon : null;
const rightIconEl = rightIcon ? rightIcon : null;
return (
<>
@@ -32,7 +32,7 @@ it(`Applies class for danger intent`, () => {
it(`Applies class for primary intent`, () => {
render(<Callout intent={Intent.Primary} />);
expect(screen.getByTestId('callout')).toHaveClass('border-neutral-500');
expect(screen.getByTestId('callout')).toHaveClass('border-vega-blue-300');
});
it(`Applies class for none intent`, () => {
+29 -9
View File
@@ -1,23 +1,31 @@
import * as TabsPrimitive from '@radix-ui/react-tabs';
import { useLocalStorageSnapshot } from '@vegaprotocol/react-helpers';
import classNames from 'classnames';
import type { ReactElement, ReactNode } from 'react';
import { Children, isValidElement, useState } from 'react';
interface TabsProps {
export interface TabsProps extends TabsPrimitive.TabsProps {
children: ReactElement<TabProps>[];
active?: string;
}
export const Tabs = ({ children, active: activeDefaultId }: TabsProps) => {
export const Tabs = ({
children,
defaultValue,
value,
onValueChange,
...props
}: TabsProps) => {
const [activeTab, setActiveTab] = useState<string>(() => {
return activeDefaultId ?? children[0].props.id;
if (defaultValue) {
return defaultValue;
}
return children[0].props.id;
});
return (
<TabsPrimitive.Root
value={activeTab}
{...props}
value={value || activeTab}
onValueChange={onValueChange || setActiveTab}
className="h-full grid grid-rows-[min-content_1fr]"
onValueChange={(value) => setActiveTab(value)}
>
<div className="border-b border-default">
<TabsPrimitive.List
@@ -26,7 +34,7 @@ export const Tabs = ({ children, active: activeDefaultId }: TabsProps) => {
>
{Children.map(children, (child) => {
if (!isValidElement(child) || child.props.hidden) return null;
const isActive = child.props.id === activeTab;
const isActive = child.props.id === (value || activeTab);
const triggerClass = classNames(
'relative px-4 py-1 border-r border-default',
'uppercase',
@@ -82,3 +90,15 @@ interface TabProps {
export const Tab = ({ children, ...props }: TabProps) => {
return <div {...props}>{children}</div>;
};
export const LocalStoragePersistTabs = ({
storageKey,
...props
}: Omit<TabsProps, 'value' | 'onValueChange'> & { storageKey: string }) => {
const [value, onValueChange] = useLocalStorageSnapshot(
`active-tab-${storageKey}`
);
return (
<Tabs {...props} value={value || undefined} onValueChange={onValueChange} />
);
};
+4 -4
View File
@@ -11,7 +11,8 @@ export const getIntentBorder = (intent = Intent.None) => {
border: true,
'border-danger': intent === Intent.Danger,
'border-warning': intent === Intent.Warning,
'border-neutral-500': intent === Intent.None || intent === Intent.Primary,
'border-neutral-500': intent === Intent.None,
'border-vega-blue-300': intent === Intent.Primary,
'border-success': intent === Intent.Success,
};
};
@@ -20,7 +21,7 @@ export const getIntentBackground = (intent?: Intent) => {
return {
'bg-neutral-200 dark:bg-neutral-800': intent === undefined,
'bg-black dark:bg-white': intent === Intent.None,
'bg-vega-pink dark:bg-vega-yellow': intent === Intent.Primary,
'bg-vega-blue-300 dark:bg-vega-blue-700': intent === Intent.Primary,
'bg-danger': intent === Intent.Danger,
'bg-warning': intent === Intent.Warning,
// contrast issues with light mode
@@ -31,8 +32,7 @@ export const getIntentBackground = (intent?: Intent) => {
export const getIntentText = (intent?: Intent) => {
return {
'text-white dark:text-black': intent === Intent.None,
'text-black dark:text-black-normal': intent === Intent.Primary,
'text-white': intent === Intent.Danger,
'text-white': intent === Intent.Danger || intent === Intent.Primary,
'text-black': intent === Intent.Warning || intent === Intent.Success,
};
};
@@ -98,6 +98,8 @@ interface ProposalNewMarketTerms {
decimalPlaces: string;
positionDecimalPlaces: string;
lpPriceRange: string;
linearSlippageFactor: string;
quadraticSlippageFactor: string;
instrument: {
name: string;
code: string;
@@ -130,6 +132,8 @@ interface ProposalUpdateMarketTerms {
updateMarket: {
marketId: string;
changes: {
linearSlippageFactor: string;
quadraticSlippageFactor: string;
instrument: {
code: string;
future: {
+1 -1
View File
@@ -27,7 +27,7 @@ export const Web3ConnectDialog = ({
<Dialog
open={dialogOpen}
onChange={setDialogOpen}
intent={Intent.Primary}
intent={Intent.None}
title={t('Connect to your Ethereum wallet')}
>
<ul data-testid="web3-connector-list">