Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0316cbd9a | ||
|
|
015e0188ec | ||
|
|
228236c152 | ||
|
|
cd6e906dc6 | ||
|
|
8a47a92dbd | ||
|
|
af14c27df9 | ||
|
|
38edcd88c0 | ||
|
|
a74cf02030 | ||
|
|
6686755763 | ||
|
|
79cf8163be | ||
|
|
1688e26f4b | ||
|
|
5e101a8eaf | ||
|
|
65071f57d0 | ||
|
|
b87c981a90 | ||
|
|
b509e5b347 | ||
|
|
bb9fce4831 | ||
|
|
918825aca8 | ||
|
|
74bf183fad | ||
|
|
674baa1ee3 | ||
|
|
d18a0c7e15 | ||
|
|
76a6005b77 | ||
|
|
79414614c2 |
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: Feature Epic
|
name: Feature Epic
|
||||||
|
about: A template to capture and scope user requirements, high level process, and basic mockups for an upcoming feature as part of the initial core spec review process.
|
||||||
title: 'Epic: '
|
title: 'Epic: '
|
||||||
description: A template to capture and scope user requirements, high level process, and basic mockups for an upcoming feature as part of the initial core spec review process.
|
|
||||||
labels: feature-epic
|
labels: feature-epic
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
context('Proposal page', { tags: '@smoke' }, function () {
|
||||||
|
describe('Verify elements on page', function () {
|
||||||
|
const proposalHeading = 'proposals-heading';
|
||||||
|
const dateTimeRegex =
|
||||||
|
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
|
||||||
|
const proposalTitle = 'Add Lorem Ipsum market';
|
||||||
|
|
||||||
|
before('Create market proposal', function () {
|
||||||
|
cy.visit('/');
|
||||||
|
cy.createMarket();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Able to view proposal', function () {
|
||||||
|
cy.navigate_to('governanceProposals');
|
||||||
|
cy.getByTestId(proposalHeading).should('be.visible');
|
||||||
|
// get first proposal in list
|
||||||
|
cy.get('[row-index="0"]').within(() => {
|
||||||
|
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
|
||||||
|
cy.get_element_by_col_id('type').should('have.text', 'NewMarket');
|
||||||
|
cy.get_element_by_col_id('state').should('have.text', 'Enacted');
|
||||||
|
cy.getByTestId('vote-progress').should('be.visible');
|
||||||
|
cy.get('[col-id="cDate"]')
|
||||||
|
.invoke('text')
|
||||||
|
.should('match', dateTimeRegex);
|
||||||
|
cy.get('[col-id="eDate"]')
|
||||||
|
.invoke('text')
|
||||||
|
.should('match', dateTimeRegex);
|
||||||
|
cy.getByTestId('external-link')
|
||||||
|
.should('have.attr', 'href')
|
||||||
|
.and('contains', 'https://governance.fairground.wtf/proposals/');
|
||||||
|
cy.contains('View terms').should('exist').click();
|
||||||
|
});
|
||||||
|
cy.getByTestId('dialog-title').should('have.text', proposalTitle);
|
||||||
|
cy.get('.language-json').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Proposal page displayed on mobile', function () {
|
||||||
|
cy.common_switch_to_mobile_and_click_toggle();
|
||||||
|
cy.navigate_to('governanceProposals', true);
|
||||||
|
cy.getByTestId(proposalHeading).should('be.visible');
|
||||||
|
cy.get('[row-index="0"]').within(() => {
|
||||||
|
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"changes": {
|
||||||
|
"decimalPlaces": "5",
|
||||||
|
"positionDecimalPlaces": "5",
|
||||||
|
"linearSlippageFactor": "0.001",
|
||||||
|
"quadraticSlippageFactor": "0",
|
||||||
|
"lpPriceRange": "10",
|
||||||
|
"instrument": {
|
||||||
|
"name": "Token test market",
|
||||||
|
"code": "Token.24h",
|
||||||
|
"future": {
|
||||||
|
"settlementAsset": "816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc",
|
||||||
|
"quoteName": "fUSDC",
|
||||||
|
"dataSourceSpecForSettlementData": {
|
||||||
|
"external": {
|
||||||
|
"oracle": {
|
||||||
|
"signers": [
|
||||||
|
{
|
||||||
|
"pubKey": {
|
||||||
|
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"name": "prices.BTC.value",
|
||||||
|
"type": "TYPE_INTEGER",
|
||||||
|
"numberDecimalPlaces": "0"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"operator": "OPERATOR_GREATER_THAN",
|
||||||
|
"value": "0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dataSourceSpecForTradingTermination": {
|
||||||
|
"external": {
|
||||||
|
"oracle": {
|
||||||
|
"signers": [
|
||||||
|
{
|
||||||
|
"pubKey": {
|
||||||
|
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"name": "trading.terminated.ETH5",
|
||||||
|
"type": "TYPE_BOOLEAN"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"operator": "OPERATOR_EQUALS",
|
||||||
|
"value": "true"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dataSourceSpecBinding": {
|
||||||
|
"settlementDataProperty": "prices.BTC.value",
|
||||||
|
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": ["sector:food", "sector:materials", "source:docs.vega.xyz"],
|
||||||
|
"priceMonitoringParameters": {
|
||||||
|
"triggers": [
|
||||||
|
{
|
||||||
|
"horizon": "43200",
|
||||||
|
"probability": "0.9999999",
|
||||||
|
"auctionExtension": "600"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"liquidityMonitoringParameters": {
|
||||||
|
"targetStakeParameters": {
|
||||||
|
"timeWindow": "3600",
|
||||||
|
"scalingFactor": 10
|
||||||
|
},
|
||||||
|
"triggeringRatio": "0.7",
|
||||||
|
"auctionExtension": "1"
|
||||||
|
},
|
||||||
|
"logNormal": {
|
||||||
|
"tau": 0.0001140771161,
|
||||||
|
"riskAversionParameter": 0.001,
|
||||||
|
"params": {
|
||||||
|
"mu": 0,
|
||||||
|
"r": 0.016,
|
||||||
|
"sigma": 0.8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"successor": {
|
||||||
|
"parentMarketId": "",
|
||||||
|
"insurancePoolFraction": "0.75"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -220,7 +220,7 @@ context(
|
|||||||
function () {
|
function () {
|
||||||
const proposalTitle = 'Test new market proposal';
|
const proposalTitle = 'Test new market proposal';
|
||||||
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
|
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
|
||||||
cy.getByTestId(newProposalTitle).type('Test new market proposal');
|
cy.getByTestId(newProposalTitle).type(proposalTitle);
|
||||||
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
|
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
|
||||||
cy.fixture('/proposals/new-market').then((newMarketProposal) => {
|
cy.fixture('/proposals/new-market').then((newMarketProposal) => {
|
||||||
const newMarketPayload = JSON.stringify(newMarketProposal);
|
const newMarketPayload = JSON.stringify(newMarketProposal);
|
||||||
@@ -606,6 +606,94 @@ context(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('able to submit successor market proposal', function () {
|
||||||
|
const proposalTitle = 'Test successor market proposal';
|
||||||
|
|
||||||
|
cy.createMarket();
|
||||||
|
cy.reload();
|
||||||
|
waitForSpinner();
|
||||||
|
cy.getByTestId('closed-proposals').within(() => {
|
||||||
|
cy.contains('Add Lorem Ipsum market')
|
||||||
|
.parentsUntil(proposalListItem)
|
||||||
|
.last()
|
||||||
|
.within(() => {
|
||||||
|
cy.getByTestId(viewProposalBtn).click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
getProposalInformationFromTable('ID').invoke('text').as('parentMarketId');
|
||||||
|
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
|
||||||
|
cy.getByTestId(newProposalTitle).type(proposalTitle);
|
||||||
|
cy.getByTestId(newProposalDescription).type(
|
||||||
|
'E2E test for successor market'
|
||||||
|
);
|
||||||
|
cy.fixture('/proposals/successor-market').then((newMarketProposal) => {
|
||||||
|
newMarketProposal.changes.successor.parentMarketId =
|
||||||
|
this.parentMarketId;
|
||||||
|
const newMarketPayload = JSON.stringify(newMarketProposal);
|
||||||
|
cy.getByTestId(newProposalTerms).type(newMarketPayload, {
|
||||||
|
parseSpecialCharSequences: false,
|
||||||
|
delay: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
cy.getByTestId(proposalDownloadBtn)
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
cy.wrap(
|
||||||
|
getDownloadedProposalJsonPath('vega-new-market-proposal-')
|
||||||
|
).then((filePath) => {
|
||||||
|
goToMakeNewProposal(governanceProposalType.RAW);
|
||||||
|
submitUniqueRawProposal({ proposalBody: filePath });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
navigateTo(navigation.proposals);
|
||||||
|
getProposalFromTitle(proposalTitle).within(() => {
|
||||||
|
// 3003-PMAN-008
|
||||||
|
cy.getByTestId('proposal-successor-info')
|
||||||
|
.should('have.text', 'Successor market to: TEST.24h')
|
||||||
|
.find('a')
|
||||||
|
.should('have.attr', 'href')
|
||||||
|
.and('contain', this.parentMarketId);
|
||||||
|
cy.getByTestId('view-proposal-btn').click();
|
||||||
|
});
|
||||||
|
// #3003-PMAN-010
|
||||||
|
cy.getByTestId(proposalJsonToggle).click();
|
||||||
|
cy.get('.language-json').within(() => {
|
||||||
|
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
|
||||||
|
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
|
||||||
|
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
|
||||||
|
cy.get('.hljs-string').should('contain.text', '0.75');
|
||||||
|
});
|
||||||
|
cy.getByTestId('proposal-market-data').within(() => {
|
||||||
|
cy.getByTestId('proposal-market-data-toggle').click();
|
||||||
|
cy.contains('Key details').click();
|
||||||
|
// 3003-PMAN-009
|
||||||
|
getMarketProposalDetailsFromTable('Parent Market ID').should(
|
||||||
|
'have.text',
|
||||||
|
this.parentMarketId
|
||||||
|
);
|
||||||
|
getMarketProposalDetailsFromTable('Insurance Pool Fraction').should(
|
||||||
|
'have.text',
|
||||||
|
'0.75'
|
||||||
|
);
|
||||||
|
getMarketProposalDetailsFromTable('Trading Mode').should(
|
||||||
|
'have.text',
|
||||||
|
'No trading'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// 3003-PMAN-011
|
||||||
|
cy.contains('Parent Market ID').realHover();
|
||||||
|
cy.getByTestId('tooltip-content').should(
|
||||||
|
'contain.text',
|
||||||
|
'The ID of the market this market succeeds.'
|
||||||
|
);
|
||||||
|
cy.contains('Insurance Pool Fraction').realHover();
|
||||||
|
cy.getByTestId('tooltip-content').should(
|
||||||
|
'contain.text',
|
||||||
|
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
after('Disassociate from second wallet key if present', function () {
|
after('Disassociate from second wallet key if present', function () {
|
||||||
cy.reload();
|
cy.reload();
|
||||||
waitForSpinner();
|
waitForSpinner();
|
||||||
|
|||||||
@@ -96,12 +96,14 @@ context('rewards - flow', { tags: '@slow' }, function () {
|
|||||||
.within(() => {
|
.within(() => {
|
||||||
cy.get('h2').first().should('contain.text', 'EPOCH');
|
cy.get('h2').first().should('contain.text', 'EPOCH');
|
||||||
cy.getByTestId('individual-rewards-asset').should('have.text', 'Vega');
|
cy.getByTestId('individual-rewards-asset').should('have.text', 'Vega');
|
||||||
cy.getByTestId('ACCOUNT_TYPE_GLOBAL_REWARD', rewardsTimeOut)
|
cy.getByTestId('ACCOUNT_TYPE_GLOBAL_REWARD', rewardsTimeOut).should(
|
||||||
.should('contain.text', '0.4415')
|
'contain.text',
|
||||||
.and('contain.text', '(44.15%)');
|
'0.4415'
|
||||||
cy.getByTestId('ACCOUNT_TYPE_FEES_INFRASTRUCTURE')
|
);
|
||||||
.should('contain.text', '0.0004')
|
cy.getByTestId('ACCOUNT_TYPE_FEES_INFRASTRUCTURE').should(
|
||||||
.and('contain.text', '(44.15%)');
|
'contain.text',
|
||||||
|
'0.0004'
|
||||||
|
);
|
||||||
cy.getByTestId('total').should('have.text', '0.4419');
|
cy.getByTestId('total').should('have.text', '0.4419');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ context(
|
|||||||
cy.getByTestId(vegaWalletCurrencyTitle)
|
cy.getByTestId(vegaWalletCurrencyTitle)
|
||||||
.contains(name)
|
.contains(name)
|
||||||
.parent()
|
.parent()
|
||||||
.siblings()
|
.siblings(txTimeout)
|
||||||
.should((elementAmount) => {
|
.should((elementAmount) => {
|
||||||
const displayedAmount = parseFloat(elementAmount.text());
|
const displayedAmount = parseFloat(elementAmount.text());
|
||||||
expect(displayedAmount).be.gte(expectedAmount);
|
expect(displayedAmount).be.gte(expectedAmount);
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export function dissociateFromSecondWalletKey() {
|
|||||||
cy.getByTestId('vega-in-wallet')
|
cy.getByTestId('vega-in-wallet')
|
||||||
.first()
|
.first()
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.getByTestId('eth-wallet-associated-balances')
|
cy.getByTestId('eth-wallet-associated-balances', txTimeout)
|
||||||
.last()
|
.last()
|
||||||
.within(() => {
|
.within(() => {
|
||||||
cy.getByTestId('associated-key')
|
cy.getByTestId('associated-key')
|
||||||
|
|||||||
@@ -78,13 +78,21 @@ export function stakingPageAssociateTokens(
|
|||||||
}
|
}
|
||||||
cy.get(tokenAmountInputBox, epochTimeout).type(amount);
|
cy.get(tokenAmountInputBox, epochTimeout).type(amount);
|
||||||
if (approve) {
|
if (approve) {
|
||||||
cy.get(tokenInputApprove, txTimeout).should('be.enabled').click();
|
cy.getByTestId('wallet-associate').then((walletAssociateField) => {
|
||||||
cy.contains('Approve $VEGA Tokens for staking on Vega').should(
|
if (
|
||||||
'be.visible'
|
walletAssociateField.find('[data-testid="token-input-approve-button"]')
|
||||||
);
|
.length
|
||||||
cy.contains('Approve $VEGA Tokens for staking on Vega', txTimeout).should(
|
) {
|
||||||
'not.exist'
|
cy.get(tokenInputApprove, txTimeout).should('be.enabled').click();
|
||||||
);
|
cy.contains('Approve $VEGA Tokens for staking on Vega').should(
|
||||||
|
'be.visible'
|
||||||
|
);
|
||||||
|
cy.contains(
|
||||||
|
'Approve $VEGA Tokens for staking on Vega',
|
||||||
|
txTimeout
|
||||||
|
).should('not.exist');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
cy.get(tokenSubmitButton, txTimeout).should('be.enabled').click();
|
cy.get(tokenSubmitButton, txTimeout).should('be.enabled').click();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -200,7 +200,7 @@ export const ProposalMarketData = ({
|
|||||||
title={marketData.tradableInstrument.instrument.code}
|
title={marketData.tradableInstrument.instrument.code}
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
onChange={(isOpen) => (isOpen ? open() : close())}
|
onChange={(isOpen) => (isOpen ? open() : close())}
|
||||||
size="medium"
|
size="large"
|
||||||
dataTestId="market-json-dialog"
|
dataTestId="market-json-dialog"
|
||||||
>
|
>
|
||||||
<CopyWithTooltip text={JSON.stringify(marketData)}>
|
<CopyWithTooltip text={JSON.stringify(marketData)}>
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ const ROWS = [
|
|||||||
|
|
||||||
export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
|
export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
|
||||||
return (
|
return (
|
||||||
<Dialog size="medium" open={isOpen} onChange={onChange}>
|
<Dialog size="large" open={isOpen} onChange={onChange}>
|
||||||
<h1 className="text-2xl mb-5 pr-2 font-medium font-alpha uppercase">
|
<h1 className="text-2xl mb-5 pr-2 font-medium font-alpha uppercase">
|
||||||
{t('Health')}
|
{t('Health')}
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ describe('charts', { tags: '@smoke' }, () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.visit('/#/markets/market-0');
|
cy.visit('/#/markets/market-0');
|
||||||
cy.wait('@Markets');
|
cy.wait('@Markets');
|
||||||
cy.getByTestId('Depth').click();
|
cy.getByTestId('Depth').click();
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
|||||||
const specDataConnection = createDataConnection();
|
const specDataConnection = createDataConnection();
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.mockGQL((req) => {
|
cy.mockGQL((req) => {
|
||||||
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
||||||
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
||||||
@@ -455,6 +456,7 @@ describe('no closed markets', { tags: '@smoke', testIsolation: true }, () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.visit('/#/markets/all');
|
cy.visit('/#/markets/all');
|
||||||
cy.get('[data-testid="Closed markets"]').click();
|
cy.get('[data-testid="Closed markets"]').click();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const nodeHealthTrigger = 'node-health-trigger';
|
|||||||
|
|
||||||
describe('home', { tags: '@regression' }, () => {
|
describe('home', { tags: '@regression' }, () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
|||||||
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
|
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
|
||||||
const dialogContent = 'dialog-content';
|
const dialogContent = 'welcome-dialog';
|
||||||
|
|
||||||
const generateProposal = (code: string): ProposalListFieldsFragment => ({
|
const generateProposal = (code: string): ProposalListFieldsFragment => ({
|
||||||
__typename: 'Proposal',
|
__typename: 'Proposal',
|
||||||
@@ -140,43 +140,43 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
cy.wait('@MarketsData');
|
cy.wait('@MarketsData');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('redirects to market/all and displays welcome notice', () => {
|
it('close welcome dialog should redirect to market/all', () => {
|
||||||
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||||
cy.getByTestId('welcome-notice-title').should(
|
cy.getByTestId('welcome-dialog').should('be.visible');
|
||||||
'contain.text',
|
cy.getByTestId('welcome-title').should('contain.text', 'Console CUSTOM');
|
||||||
'Welcome to Console'
|
cy.getByTestId('browse-markets-button').should('not.be.disabled');
|
||||||
);
|
cy.getByTestId('get-started-banner').should('be.visible');
|
||||||
});
|
cy.getByTestId('get-started-button').should('not.be.disabled');
|
||||||
});
|
cy.getByTestId('dialog-close').click();
|
||||||
|
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||||
describe('no proposal nor markets found', () => {
|
cy.window().then((window) => {
|
||||||
it('there are welcome text and a link to propose market', () => {
|
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
|
||||||
cy.mockGQL((req) => {
|
'true'
|
||||||
const data = {
|
);
|
||||||
marketsConnection: {
|
|
||||||
__typename: 'MarketConnection',
|
|
||||||
edges: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
aliasGQLQuery(req, 'Markets', data);
|
|
||||||
aliasGQLQuery(req, 'MarketsData', data);
|
|
||||||
aliasGQLQuery(req, 'ProposalsList', {
|
|
||||||
proposalsConnection: {
|
|
||||||
__typename: 'ProposalsConnection',
|
|
||||||
edges: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
cy.visit('/');
|
});
|
||||||
cy.wait('@Markets');
|
|
||||||
cy.wait('@MarketsData');
|
it('click browse markets button should redirect to market/all', () => {
|
||||||
cy.getByTestId('welcome-notice-title').should(
|
cy.getByTestId('welcome-dialog').should('be.visible');
|
||||||
'contain.text',
|
cy.getByTestId('browse-markets-button').click();
|
||||||
'Welcome to Console'
|
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||||
);
|
cy.window().then((window) => {
|
||||||
cy.getByTestId('external-link')
|
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
|
||||||
.contains('Propose a market')
|
'true'
|
||||||
.should('exist');
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('click get started button should open connect dialog', () => {
|
||||||
|
cy.getByTestId('welcome-dialog').should('be.visible');
|
||||||
|
cy.getByTestId('get-started-button').click();
|
||||||
|
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||||
|
cy.window().then((window) => {
|
||||||
|
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
|
||||||
|
'true'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
cy.getByTestId('wallet-dialog-title').should('contain.text', 'Connect');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
cy.window().then((window) => {
|
cy.window().then((window) => {
|
||||||
window.localStorage.setItem('marketId', 'market-1');
|
window.localStorage.setItem('marketId', 'market-1');
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.wait('@Markets');
|
cy.getByTestId('dialog-close').click();
|
||||||
cy.location('hash').should('equal', '#/markets/market-1');
|
cy.location('hash').should('equal', '#/markets/market-1');
|
||||||
cy.getByTestId(dialogContent).should('not.exist');
|
cy.getByTestId(dialogContent).should('not.exist');
|
||||||
});
|
});
|
||||||
@@ -199,6 +199,7 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
});
|
});
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.wait('@Markets');
|
cy.wait('@Markets');
|
||||||
|
cy.getByTestId('dialog-close').click();
|
||||||
cy.location('hash').should('equal', '#/markets/market-not-existing');
|
cy.location('hash').should('equal', '#/markets/market-not-existing');
|
||||||
cy.getByTestId(dialogContent).should('not.exist');
|
cy.getByTestId(dialogContent).should('not.exist');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import type { MarketsQuery } from '@vegaprotocol/markets';
|
|||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
|
||||||
const rowSelector =
|
const rowSelector =
|
||||||
'[data-testid="tab-all-markets"] .ag-center-cols-container .ag-row';
|
'[data-testid="tab-open-markets"] .ag-center-cols-container .ag-row';
|
||||||
const colInstrumentCode = '[col-id="tradableInstrument.instrument.code"]';
|
const colInstrumentCode = '[col-id="tradableInstrument.instrument.code"]';
|
||||||
|
|
||||||
describe('markets all table', { tags: '@smoke' }, () => {
|
describe('markets all table', { tags: '@smoke' }, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.clearLocalStorage().then(() => {
|
cy.clearLocalStorage().then(() => {
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.mockTradingPage(
|
cy.mockTradingPage(
|
||||||
Schema.MarketState.STATE_ACTIVE,
|
Schema.MarketState.STATE_ACTIVE,
|
||||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||||
@@ -34,7 +35,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
|||||||
'Settlement asset',
|
'Settlement asset',
|
||||||
'',
|
'',
|
||||||
];
|
];
|
||||||
cy.getByTestId('tab-all-markets').within(($headers) => {
|
cy.getByTestId('tab-open-markets').within(($headers) => {
|
||||||
cy.wrap($headers)
|
cy.wrap($headers)
|
||||||
.get('.ag-header-cell-text')
|
.get('.ag-header-cell-text')
|
||||||
.each(($header, i) => {
|
.each(($header, i) => {
|
||||||
@@ -44,7 +45,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('markets tab should be rendered properly', () => {
|
it('markets tab should be rendered properly', () => {
|
||||||
cy.get('[data-testid="All markets"]').should(
|
cy.get('[data-testid="Open markets"]').should(
|
||||||
'have.attr',
|
'have.attr',
|
||||||
'data-state',
|
'data-state',
|
||||||
'active'
|
'active'
|
||||||
@@ -166,7 +167,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
|||||||
'ETHBTC.QM21',
|
'ETHBTC.QM21',
|
||||||
'SOLUSD',
|
'SOLUSD',
|
||||||
];
|
];
|
||||||
cy.get('[data-testid="All markets"]').click({ force: true });
|
cy.get('[data-testid="Open markets"]').click({ force: true });
|
||||||
cy.url().should('eq', Cypress.config('baseUrl') + '/#/markets/all');
|
cy.url().should('eq', Cypress.config('baseUrl') + '/#/markets/all');
|
||||||
cy.contains('AAPL.MF21').should('be.visible');
|
cy.contains('AAPL.MF21').should('be.visible');
|
||||||
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
|
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
|
||||||
@@ -191,7 +192,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
|
describe('no open markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
const markets: MarketsQuery = {};
|
const markets: MarketsQuery = {};
|
||||||
@@ -199,11 +200,12 @@ describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
|
|||||||
aliasGQLQuery(req, 'Markets', markets);
|
aliasGQLQuery(req, 'Markets', markets);
|
||||||
});
|
});
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.visit('/#/markets/all');
|
cy.visit('/#/markets/all');
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('can see no markets message', () => {
|
it.skip('can see no markets message', () => {
|
||||||
// 6001-MARK-048
|
// 6001-MARK-048
|
||||||
cy.getByTestId('tab-all-markets').should('contain.text', 'No markets');
|
cy.getByTestId('tab-open-markets').should('contain.text', 'No markets');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.mockTradingPage(MarketState.STATE_ACTIVE);
|
cy.mockTradingPage(MarketState.STATE_ACTIVE);
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
cy.visit('/#/markets/market-0');
|
cy.visit('/#/markets/market-0');
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const headers = [
|
|||||||
|
|
||||||
describe('liquidity table - trading', { tags: '@smoke' }, () => {
|
describe('liquidity table - trading', { tags: '@smoke' }, () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
cy.mockTradingPage(
|
cy.mockTradingPage(
|
||||||
Schema.MarketState.STATE_ACTIVE,
|
Schema.MarketState.STATE_ACTIVE,
|
||||||
@@ -119,6 +120,7 @@ describe('liquidity table - trading', { tags: '@smoke' }, () => {
|
|||||||
|
|
||||||
describe('liquidity table view', { tags: '@smoke' }, () => {
|
describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
cy.mockTradingPage(
|
cy.mockTradingPage(
|
||||||
Schema.MarketState.STATE_ACTIVE,
|
Schema.MarketState.STATE_ACTIVE,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
|||||||
cy.window().then((window) => {
|
cy.window().then((window) => {
|
||||||
window.localStorage.setItem('marketId', 'market-1');
|
window.localStorage.setItem('marketId', 'market-1');
|
||||||
});
|
});
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.mockTradingPage(
|
cy.mockTradingPage(
|
||||||
MarketState.STATE_ACTIVE,
|
MarketState.STATE_ACTIVE,
|
||||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ describe('Market trading page', () => {
|
|||||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||||
);
|
);
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.visit('/#/markets/market-0');
|
cy.visit('/#/markets/market-0');
|
||||||
cy.wait('@MarketData');
|
cy.wait('@MarketData');
|
||||||
cy.getByTestId(marketSummaryBlock).should('be.visible');
|
cy.getByTestId(marketSummaryBlock).should('be.visible');
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.visit('/#/markets/all');
|
cy.visit('/#/markets/all');
|
||||||
cy.get('[data-testid="Proposed markets"]').click();
|
cy.get('[data-testid="Proposed markets"]').click();
|
||||||
});
|
});
|
||||||
@@ -213,6 +214,7 @@ describe('no markets proposed', { tags: '@smoke', testIsolation: true }, () => {
|
|||||||
aliasGQLQuery(req, 'ProposalsList', proposal);
|
aliasGQLQuery(req, 'ProposalsList', proposal);
|
||||||
});
|
});
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can see no markets message', () => {
|
it('can see no markets message', () => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ describe('markets table', { tags: '@smoke' }, () => {
|
|||||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||||
);
|
);
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.visit('/#/markets/all');
|
cy.visit('/#/markets/all');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const oracleFullProfile = 'oracle-full-profile';
|
|||||||
|
|
||||||
describe('oracle information', { tags: '@smoke' }, () => {
|
describe('oracle information', { tags: '@smoke' }, () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.mockTradingPage(
|
cy.mockTradingPage(
|
||||||
MarketState.STATE_ACTIVE,
|
MarketState.STATE_ACTIVE,
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const resPrice = 'price-990';
|
|||||||
|
|
||||||
describe('order book', { tags: '@smoke' }, () => {
|
describe('order book', { tags: '@smoke' }, () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
cy.visit('/#/markets/market-0');
|
cy.visit('/#/markets/market-0');
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ describe('Settings page', { tags: '@smoke' }, () => {
|
|||||||
|
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
|
||||||
// Only click if not already active otherwise sidebar will close
|
// Only click if not already active otherwise sidebar will close
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ function getButtonSelectorByText(text: string): string {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.visit('/#/markets/market-0');
|
cy.visit('/#/markets/market-0');
|
||||||
cy.wait('@Markets');
|
cy.wait('@Markets');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
|||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
cy.clearAllLocalStorage();
|
cy.clearAllLocalStorage();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.visit('/#/markets/market-0');
|
cy.visit('/#/markets/market-0');
|
||||||
cy.wait('@Markets');
|
cy.wait('@Markets');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ describe('time in force validation', { tags: '@smoke' }, () => {
|
|||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
cy.clearAllLocalStorage();
|
cy.clearAllLocalStorage();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.visit('/#/markets/market-0');
|
cy.visit('/#/markets/market-0');
|
||||||
cy.wait('@Markets');
|
cy.wait('@Markets');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ describe('trades', { tags: '@smoke' }, () => {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.intercept('POST', '/graphql', (req) => {
|
cy.intercept('POST', '/graphql', (req) => {
|
||||||
if (req.body.operationName === 'Trades') {
|
if (req.body.operationName === 'Trades') {
|
||||||
req.alias = '@Trades';
|
req.alias = '@Trades';
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ describe(
|
|||||||
cy.visit('/#/portfolio');
|
cy.visit('/#/portfolio');
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,6 +123,7 @@ describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
|
|||||||
cy.visit('/#/portfolio');
|
cy.visit('/#/portfolio');
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
|
cy.setOnBoardingViewed();
|
||||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
import { selectAsset } from '../support/helpers';
|
||||||
|
|
||||||
|
const amountField = 'input[name="amount"]';
|
||||||
|
const transferText = 'transfer-intro-text';
|
||||||
|
const errorText = 'input-error-text';
|
||||||
|
const formFieldError = 'input-error-text';
|
||||||
|
const includeTransferFeeRadioBtn = 'include-transfer-fee';
|
||||||
|
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
|
||||||
|
const manageVegaWallet = 'manage-vega-wallet';
|
||||||
|
const submitTransferBtn = '[type="submit"]';
|
||||||
|
const toAddressField = '[name="toAddress"]';
|
||||||
|
const totalTransferfee = 'total-transfer-fee';
|
||||||
|
const transferAmount = 'transfer-amount';
|
||||||
|
const transferForm = 'transfer-form';
|
||||||
|
const transferFee = 'transfer-fee';
|
||||||
|
const walletTransfer = 'wallet-transfer';
|
||||||
|
|
||||||
|
const ASSET_EURO = 1;
|
||||||
|
const ASSET_SEPOLIA_TBTC = 2;
|
||||||
|
|
||||||
|
describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.mockWeb3Provider();
|
||||||
|
cy.mockTradingPage();
|
||||||
|
cy.mockSubscription();
|
||||||
|
cy.setVegaWallet();
|
||||||
|
|
||||||
|
cy.visit('/');
|
||||||
|
cy.getByTestId(manageVegaWallet).click();
|
||||||
|
cy.getByTestId(walletTransfer).click();
|
||||||
|
|
||||||
|
cy.wait('@Assets');
|
||||||
|
cy.wait('@Accounts');
|
||||||
|
|
||||||
|
cy.mockVegaWalletTransaction();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('transfer fees tooltips', () => {
|
||||||
|
// 1003-TRAN-015
|
||||||
|
// 1003-TRAN-016
|
||||||
|
// 1003-TRAN-017
|
||||||
|
// 1003-TRAN-018
|
||||||
|
// 1003-TRAN-019
|
||||||
|
cy.getByTestId(transferForm);
|
||||||
|
cy.contains('Enter manually').click();
|
||||||
|
cy.getByTestId(transferForm)
|
||||||
|
.find(toAddressField)
|
||||||
|
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
||||||
|
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||||
|
cy.getByTestId(transferForm)
|
||||||
|
.find(amountField)
|
||||||
|
.type('1', { delay: 100, force: true });
|
||||||
|
|
||||||
|
/// Check Include Transfer Fee tooltip
|
||||||
|
cy.get('label[for="include-transfer-fee"] div').realHover();
|
||||||
|
cy.get('[data-side="bottom"] div')
|
||||||
|
.should('be.visible')
|
||||||
|
.should('not.be.empty');
|
||||||
|
|
||||||
|
//Check Transfer Fee tooltip
|
||||||
|
cy.contains('div', 'Transfer fee').realHover();
|
||||||
|
cy.get('[data-side="bottom"] div')
|
||||||
|
.should('be.visible')
|
||||||
|
.should('not.be.empty');
|
||||||
|
|
||||||
|
//Check Amount to be transferred tooltip
|
||||||
|
cy.contains('div', 'Amount to be transferred').realHover();
|
||||||
|
cy.get('[data-side="bottom"] div')
|
||||||
|
.should('be.visible')
|
||||||
|
.should('not.be.empty');
|
||||||
|
|
||||||
|
//Check Total amount (with fee) tooltip
|
||||||
|
cy.contains('div', 'Total amount (with fee)').realHover();
|
||||||
|
cy.get('[data-side="bottom"] div')
|
||||||
|
.should('be.visible')
|
||||||
|
.should('not.be.empty');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('transfer fees', () => {
|
||||||
|
// 1003-TRAN-020
|
||||||
|
// 1003-TRAN-021
|
||||||
|
// 1003-TRAN-022
|
||||||
|
// 1003-TRAN-023
|
||||||
|
cy.getByTestId(transferForm);
|
||||||
|
cy.contains('Enter manually').click();
|
||||||
|
|
||||||
|
cy.getByTestId(transferForm)
|
||||||
|
.find(toAddressField)
|
||||||
|
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
||||||
|
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||||
|
cy.getByTestId(includeTransferFeeRadioBtn).should('be.disabled');
|
||||||
|
|
||||||
|
cy.getByTestId(transferForm)
|
||||||
|
.find(amountField)
|
||||||
|
.type('1', { delay: 100, force: true });
|
||||||
|
|
||||||
|
cy.getByTestId(transferFee)
|
||||||
|
.should('be.visible')
|
||||||
|
.should('contain.text', '0.01');
|
||||||
|
cy.getByTestId(transferAmount)
|
||||||
|
.should('be.visible')
|
||||||
|
.should('contain.text', '1.00');
|
||||||
|
cy.getByTestId(totalTransferfee)
|
||||||
|
.should('be.visible')
|
||||||
|
.should('contain.text', '1.01');
|
||||||
|
cy.getByTestId(includeTransferFeeRadioBtn).click();
|
||||||
|
cy.getByTestId(transferFee)
|
||||||
|
.should('be.visible')
|
||||||
|
.should('contain.text', '0.01');
|
||||||
|
cy.getByTestId(transferAmount)
|
||||||
|
.should('be.visible')
|
||||||
|
.should('contain.text', '0.99');
|
||||||
|
cy.getByTestId(totalTransferfee)
|
||||||
|
.should('be.visible')
|
||||||
|
.should('contain.text', '1.00');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(
|
||||||
|
'transfer form validation',
|
||||||
|
{ tags: '@regression', testIsolation: true },
|
||||||
|
() => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.mockWeb3Provider();
|
||||||
|
cy.mockTradingPage();
|
||||||
|
cy.mockSubscription();
|
||||||
|
cy.setVegaWallet();
|
||||||
|
|
||||||
|
cy.visit('/#/portfolio');
|
||||||
|
cy.getByTestId(manageVegaWallet).click();
|
||||||
|
cy.getByTestId(walletTransfer).click();
|
||||||
|
|
||||||
|
cy.wait('@Accounts');
|
||||||
|
cy.wait('@Assets');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('transfer Text', () => {
|
||||||
|
// 1003-TRAN-003
|
||||||
|
cy.getByTestId(transferText)
|
||||||
|
.should('exist')
|
||||||
|
.get(keyID)
|
||||||
|
.invoke('text')
|
||||||
|
.should('match', /[\w.]{6}…[\w.]{6}/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid vega key validation', () => {
|
||||||
|
//1003-TRAN-013
|
||||||
|
//1003-TRAN-004
|
||||||
|
cy.getByTestId(transferForm).should('be.visible');
|
||||||
|
cy.contains('Enter manually').click();
|
||||||
|
cy.getByTestId(transferForm).find(toAddressField).type('asd');
|
||||||
|
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||||
|
cy.getByTestId(formFieldError).should('contain.text', 'Invalid Vega key');
|
||||||
|
cy.contains('label', 'Vega key').should('be.visible');
|
||||||
|
cy.contains('label', 'Asset').should('be.visible');
|
||||||
|
cy.contains('label', 'Amount').should('be.visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('empty fields', () => {
|
||||||
|
// 1003-TRAN-012
|
||||||
|
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||||
|
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
||||||
|
cy.getByTestId(formFieldError).should('have.length', 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('min amount', () => {
|
||||||
|
// 1002-WITH-010
|
||||||
|
// 1003-TRAN-014
|
||||||
|
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||||
|
cy.get(amountField).clear().type('0');
|
||||||
|
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||||
|
cy.getByTestId(errorText).should(
|
||||||
|
'contain.text',
|
||||||
|
'Value is below minimum'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('max amount', () => {
|
||||||
|
// 1003-TRAN-002
|
||||||
|
// 1003-TRAN-011
|
||||||
|
// 1003-TRAN-002
|
||||||
|
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
|
||||||
|
cy.get(amountField).clear().type('1001', { delay: 100 });
|
||||||
|
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||||
|
cy.getByTestId(errorText).should(
|
||||||
|
'contain.text',
|
||||||
|
'You cannot transfer more than your available collateral'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -5,206 +5,15 @@ const amountShortName = 'input[name="amount"] + div + span.text-xs';
|
|||||||
const assetSelection = 'select-asset';
|
const assetSelection = 'select-asset';
|
||||||
const assetBalance = 'asset-balance';
|
const assetBalance = 'asset-balance';
|
||||||
const assetOption = 'rich-select-option';
|
const assetOption = 'rich-select-option';
|
||||||
const transferText = 'transfer-intro-text';
|
|
||||||
const errorText = 'input-error-text';
|
|
||||||
const formFieldError = 'input-error-text';
|
|
||||||
const includeTransferFeeRadioBtn = 'include-transfer-fee';
|
|
||||||
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
|
|
||||||
const manageVegaWallet = 'manage-vega-wallet';
|
|
||||||
const openTransferButton = 'open-transfer';
|
const openTransferButton = 'open-transfer';
|
||||||
const submitTransferBtn = '[type="submit"]';
|
const submitTransferBtn = '[type="submit"]';
|
||||||
const toAddressField = '[name="toAddress"]';
|
const toAddressField = '[name="toAddress"]';
|
||||||
const totalTransferfee = 'total-transfer-fee';
|
|
||||||
const transferAmount = 'transfer-amount';
|
|
||||||
const transferForm = 'transfer-form';
|
const transferForm = 'transfer-form';
|
||||||
const transferFee = 'transfer-fee';
|
|
||||||
const walletTransfer = 'wallet-transfer';
|
|
||||||
|
|
||||||
const ASSET_EURO = 1;
|
|
||||||
const ASSET_SEPOLIA_TBTC = 2;
|
const ASSET_SEPOLIA_TBTC = 2;
|
||||||
|
|
||||||
const collateralTab = 'Collateral';
|
const collateralTab = 'Collateral';
|
||||||
const toastCloseBtn = 'toast-close';
|
const toastCloseBtn = 'toast-close';
|
||||||
const toastContent = 'toast-content';
|
const toastContent = 'toast-content';
|
||||||
|
|
||||||
describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.mockWeb3Provider();
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.setVegaWallet();
|
|
||||||
|
|
||||||
cy.visit('/');
|
|
||||||
cy.getByTestId(manageVegaWallet).click();
|
|
||||||
cy.getByTestId(walletTransfer).click();
|
|
||||||
|
|
||||||
cy.wait('@Assets');
|
|
||||||
cy.wait('@Accounts');
|
|
||||||
|
|
||||||
cy.mockVegaWalletTransaction();
|
|
||||||
|
|
||||||
// Only click if not already active otherwise sidebar will close
|
|
||||||
cy.get('[data-testid="sidebar-content"]').then(($sidebarContent) => {
|
|
||||||
if ($sidebarContent.find('h2').text() !== 'Transfer') {
|
|
||||||
cy.get('[data-testid="sidebar"] [data-testid="Transfer"]').click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('transfer fees tooltips', () => {
|
|
||||||
// 1003-TRAN-015
|
|
||||||
// 1003-TRAN-016
|
|
||||||
// 1003-TRAN-017
|
|
||||||
// 1003-TRAN-018
|
|
||||||
// 1003-TRAN-019
|
|
||||||
cy.getByTestId(transferForm);
|
|
||||||
cy.contains('Enter manually').click();
|
|
||||||
cy.getByTestId(transferForm)
|
|
||||||
.find(toAddressField)
|
|
||||||
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
|
||||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
|
||||||
cy.getByTestId(transferForm)
|
|
||||||
.find(amountField)
|
|
||||||
.type('1', { delay: 100, force: true });
|
|
||||||
|
|
||||||
/// Check Include Transfer Fee tooltip
|
|
||||||
cy.get('label[for="include-transfer-fee"] div').realHover();
|
|
||||||
cy.get('[data-side="bottom"] div')
|
|
||||||
.should('be.visible')
|
|
||||||
.should('not.be.empty');
|
|
||||||
|
|
||||||
//Check Transfer Fee tooltip
|
|
||||||
cy.contains('div', 'Transfer fee').realHover();
|
|
||||||
cy.get('[data-side="bottom"] div')
|
|
||||||
.should('be.visible')
|
|
||||||
.should('not.be.empty');
|
|
||||||
|
|
||||||
//Check Amount to be transferred tooltip
|
|
||||||
cy.contains('div', 'Amount to be transferred').realHover();
|
|
||||||
cy.get('[data-side="bottom"] div')
|
|
||||||
.should('be.visible')
|
|
||||||
.should('not.be.empty');
|
|
||||||
|
|
||||||
//Check Total amount (with fee) tooltip
|
|
||||||
cy.contains('div', 'Total amount (with fee)').realHover();
|
|
||||||
cy.get('[data-side="bottom"] div')
|
|
||||||
.should('be.visible')
|
|
||||||
.should('not.be.empty');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('transfer fees', () => {
|
|
||||||
// 1003-TRAN-020
|
|
||||||
// 1003-TRAN-021
|
|
||||||
// 1003-TRAN-022
|
|
||||||
// 1003-TRAN-023
|
|
||||||
cy.getByTestId(transferForm);
|
|
||||||
cy.contains('Enter manually').click();
|
|
||||||
|
|
||||||
cy.getByTestId(transferForm)
|
|
||||||
.find(toAddressField)
|
|
||||||
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
|
|
||||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
|
||||||
cy.getByTestId(includeTransferFeeRadioBtn).should('be.disabled');
|
|
||||||
|
|
||||||
cy.getByTestId(transferForm)
|
|
||||||
.find(amountField)
|
|
||||||
.type('1', { delay: 100, force: true });
|
|
||||||
|
|
||||||
cy.getByTestId(transferFee)
|
|
||||||
.should('be.visible')
|
|
||||||
.should('contain.text', '0.01');
|
|
||||||
cy.getByTestId(transferAmount)
|
|
||||||
.should('be.visible')
|
|
||||||
.should('contain.text', '1.00');
|
|
||||||
cy.getByTestId(totalTransferfee)
|
|
||||||
.should('be.visible')
|
|
||||||
.should('contain.text', '1.01');
|
|
||||||
cy.getByTestId(includeTransferFeeRadioBtn).click();
|
|
||||||
cy.getByTestId(transferFee)
|
|
||||||
.should('be.visible')
|
|
||||||
.should('contain.text', '0.01');
|
|
||||||
cy.getByTestId(transferAmount)
|
|
||||||
.should('be.visible')
|
|
||||||
.should('contain.text', '0.99');
|
|
||||||
cy.getByTestId(totalTransferfee)
|
|
||||||
.should('be.visible')
|
|
||||||
.should('contain.text', '1.00');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(
|
|
||||||
'transfer form validation',
|
|
||||||
{ tags: '@regression', testIsolation: true },
|
|
||||||
() => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.mockWeb3Provider();
|
|
||||||
cy.mockTradingPage();
|
|
||||||
cy.mockSubscription();
|
|
||||||
cy.setVegaWallet();
|
|
||||||
|
|
||||||
cy.visit('/#/portfolio');
|
|
||||||
cy.getByTestId(manageVegaWallet).click();
|
|
||||||
cy.getByTestId(walletTransfer).click();
|
|
||||||
|
|
||||||
cy.wait('@Accounts');
|
|
||||||
cy.wait('@Assets');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('transfer Text', () => {
|
|
||||||
// 1003-TRAN-003
|
|
||||||
cy.getByTestId(transferText)
|
|
||||||
.should('exist')
|
|
||||||
.get(keyID)
|
|
||||||
.invoke('text')
|
|
||||||
.should('match', /[\w.]{6}…[\w.]{6}/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('invalid vega key validation', () => {
|
|
||||||
//1003-TRAN-013
|
|
||||||
//1003-TRAN-004
|
|
||||||
cy.getByTestId(transferForm).should('be.visible');
|
|
||||||
cy.contains('Enter manually').click();
|
|
||||||
cy.getByTestId(transferForm).find(toAddressField).type('asd');
|
|
||||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
|
||||||
cy.getByTestId(formFieldError).should('contain.text', 'Invalid Vega key');
|
|
||||||
cy.contains('label', 'Vega key').should('be.visible');
|
|
||||||
cy.contains('label', 'Asset').should('be.visible');
|
|
||||||
cy.contains('label', 'Amount').should('be.visible');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('empty fields', () => {
|
|
||||||
// 1003-TRAN-012
|
|
||||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
|
||||||
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
|
||||||
cy.getByTestId(formFieldError).should('have.length', 3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('min amount', () => {
|
|
||||||
// 1002-WITH-010
|
|
||||||
// 1003-TRAN-014
|
|
||||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
|
||||||
cy.get(amountField).clear().type('0');
|
|
||||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
|
||||||
cy.getByTestId(errorText).should(
|
|
||||||
'contain.text',
|
|
||||||
'Value is below minimum'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('max amount', () => {
|
|
||||||
// 1003-TRAN-002
|
|
||||||
// 1003-TRAN-011
|
|
||||||
// 1003-TRAN-002
|
|
||||||
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
|
|
||||||
cy.get(amountField).clear().type('1001', { delay: 100 });
|
|
||||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
|
||||||
cy.getByTestId(errorText).should(
|
|
||||||
'contain.text',
|
|
||||||
'You cannot transfer more than your available collateral'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
describe('withdraw actions', { tags: '@smoke', testIsolation: true }, () => {
|
describe('withdraw actions', { tags: '@smoke', testIsolation: true }, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.mockWeb3Provider();
|
cy.mockWeb3Provider();
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
|||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
# NX_ICEBERG_ORDERS
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { DepositContainer } from '@vegaprotocol/deposits';
|
||||||
|
import { GetStarted } from '../../components/welcome-dialog';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
|
export const Deposit = () => {
|
||||||
|
return (
|
||||||
|
<div className="py-16 px-8 flex w-full justify-center">
|
||||||
|
<div className="lg:min-w-[700px] min-w-[300px] max-w-[700px]">
|
||||||
|
<h1 className="text-4xl xl:text-5xl uppercase font-alpha calt">
|
||||||
|
{t('Deposit')}
|
||||||
|
</h1>
|
||||||
|
<div className="mt-10">
|
||||||
|
<DepositContainer />
|
||||||
|
<GetStarted />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { Deposit } from './deposit';
|
||||||
|
|
||||||
|
export default Deposit;
|
||||||
@@ -23,7 +23,6 @@ export const Home = () => {
|
|||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
} else if (data) {
|
} else if (data) {
|
||||||
update({ shouldDisplayWelcomeDialog: true });
|
|
||||||
const marketDataId = data[0]?.id;
|
const marketDataId = data[0]?.id;
|
||||||
if (marketDataId) {
|
if (marketDataId) {
|
||||||
navigate(Links[Routes.MARKET](marketDataId), {
|
navigate(Links[Routes.MARKET](marketDataId), {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import type { Market } from '@vegaprotocol/markets';
|
|||||||
import { Filter } from '@vegaprotocol/orders';
|
import { Filter } from '@vegaprotocol/orders';
|
||||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
|
||||||
import {
|
import {
|
||||||
ResizableGrid,
|
ResizableGrid,
|
||||||
ResizableGridPanel,
|
ResizableGridPanel,
|
||||||
@@ -58,7 +57,11 @@ const MainGrid = memo(
|
|||||||
>
|
>
|
||||||
<TradeGridChild>
|
<TradeGridChild>
|
||||||
<Tabs storageKey="console-trade-grid-main-left">
|
<Tabs storageKey="console-trade-grid-main-left">
|
||||||
<Tab id="chart" name={t('Chart')}>
|
<Tab
|
||||||
|
id="chart"
|
||||||
|
name={t('Chart')}
|
||||||
|
menu={<TradingViews.candles.menu />}
|
||||||
|
>
|
||||||
<TradingViews.candles.component marketId={marketId} />
|
<TradingViews.candles.component marketId={marketId} />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="depth" name={t('Depth')}>
|
<Tab id="depth" name={t('Depth')}>
|
||||||
@@ -95,64 +98,48 @@ const MainGrid = memo(
|
|||||||
<TradeGridChild>
|
<TradeGridChild>
|
||||||
<Tabs storageKey="console-trade-grid-bottom">
|
<Tabs storageKey="console-trade-grid-bottom">
|
||||||
<Tab id="positions" name={t('Positions')}>
|
<Tab id="positions" name={t('Positions')}>
|
||||||
<VegaWalletContainer>
|
<TradingViews.positions.component
|
||||||
<TradingViews.positions.component
|
onMarketClick={onMarketClick}
|
||||||
onMarketClick={onMarketClick}
|
/>
|
||||||
/>
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="open-orders" name={t('Open')}>
|
<Tab id="open-orders" name={t('Open')}>
|
||||||
<VegaWalletContainer>
|
<TradingViews.orders.component
|
||||||
<TradingViews.orders.component
|
marketId={marketId}
|
||||||
marketId={marketId}
|
filter={Filter.Open}
|
||||||
filter={Filter.Open}
|
/>
|
||||||
/>
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="closed-orders" name={t('Closed')}>
|
<Tab id="closed-orders" name={t('Closed')}>
|
||||||
<VegaWalletContainer>
|
<TradingViews.orders.component
|
||||||
<TradingViews.orders.component
|
marketId={marketId}
|
||||||
marketId={marketId}
|
filter={Filter.Closed}
|
||||||
filter={Filter.Closed}
|
/>
|
||||||
/>
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||||
<VegaWalletContainer>
|
<TradingViews.orders.component
|
||||||
<TradingViews.orders.component
|
marketId={marketId}
|
||||||
marketId={marketId}
|
filter={Filter.Rejected}
|
||||||
filter={Filter.Rejected}
|
/>
|
||||||
/>
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="orders" name={t('All')}>
|
<Tab id="orders" name={t('All')}>
|
||||||
<VegaWalletContainer>
|
<TradingViews.orders.component marketId={marketId} />
|
||||||
<TradingViews.orders.component marketId={marketId} />
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
{FLAGS.STOP_ORDERS ? (
|
{FLAGS.STOP_ORDERS ? (
|
||||||
<Tab id="stop-orders" name={t('Stop orders')}>
|
<Tab id="stop-orders" name={t('Stop orders')}>
|
||||||
<VegaWalletContainer>
|
<TradingViews.stopOrders.component />
|
||||||
<TradingViews.stopOrders.component />
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
) : null}
|
) : null}
|
||||||
<Tab id="fills" name={t('Fills')}>
|
<Tab id="fills" name={t('Fills')}>
|
||||||
<VegaWalletContainer>
|
<TradingViews.fills.component
|
||||||
<TradingViews.fills.component
|
marketId={marketId}
|
||||||
marketId={marketId}
|
onMarketClick={onMarketClick}
|
||||||
onMarketClick={onMarketClick}
|
/>
|
||||||
/>
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="accounts" name={t('Collateral')}>
|
<Tab id="accounts" name={t('Collateral')}>
|
||||||
<VegaWalletContainer>
|
<TradingViews.collateral.component
|
||||||
<TradingViews.collateral.component
|
pinnedAsset={pinnedAsset}
|
||||||
pinnedAsset={pinnedAsset}
|
onMarketClick={onMarketClick}
|
||||||
onMarketClick={onMarketClick}
|
hideButtons
|
||||||
hideButtons
|
/>
|
||||||
/>
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</TradeGridChild>
|
</TradeGridChild>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const TradePanels = ({
|
|||||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||||
|
|
||||||
const [view, setView] = useState<TradingView>('candles');
|
const [view, setView] = useState<TradingView>('candles');
|
||||||
|
|
||||||
const renderView = () => {
|
const renderView = () => {
|
||||||
const Component = memo<{
|
const Component = memo<{
|
||||||
marketId: string;
|
marketId: string;
|
||||||
@@ -65,8 +66,23 @@ export const TradePanels = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderMenu = () => {
|
||||||
|
const viewCfg = TradingViews[view];
|
||||||
|
|
||||||
|
if ('menu' in viewCfg) {
|
||||||
|
const Menu = viewCfg.menu;
|
||||||
|
return (
|
||||||
|
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||||
|
<Menu />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full grid grid-rows-[min-content_1fr_min-content]">
|
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
|
||||||
<div>
|
<div>
|
||||||
{FLAGS.SUCCESSOR_MARKETS && (
|
{FLAGS.SUCCESSOR_MARKETS && (
|
||||||
<>
|
<>
|
||||||
@@ -76,6 +92,7 @@ export const TradePanels = ({
|
|||||||
)}
|
)}
|
||||||
<OracleBanner marketId={market?.id || ''} />
|
<OracleBanner marketId={market?.id || ''} />
|
||||||
</div>
|
</div>
|
||||||
|
<div>{renderMenu()}</div>
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<AutoSizer>
|
<AutoSizer>
|
||||||
{({ width, height }) => (
|
{({ width, height }) => (
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import type { ComponentProps } from 'react';
|
|||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { TradesContainer } from '@vegaprotocol/trades';
|
import { TradesContainer } from '@vegaprotocol/trades';
|
||||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
import {
|
||||||
|
CandlesChartContainer,
|
||||||
|
CandlesMenu,
|
||||||
|
} from '@vegaprotocol/candles-chart';
|
||||||
import { Filter } from '@vegaprotocol/orders';
|
import { Filter } from '@vegaprotocol/orders';
|
||||||
import { NO_MARKET } from './constants';
|
import { NO_MARKET } from './constants';
|
||||||
import { OrderbookContainer } from '../../components/orderbook-container';
|
import { OrderbookContainer } from '../../components/orderbook-container';
|
||||||
@@ -35,6 +38,7 @@ export const TradingViews = {
|
|||||||
candles: {
|
candles: {
|
||||||
label: 'Candles',
|
label: 'Candles',
|
||||||
component: requiresMarket(CandlesChartContainer),
|
component: requiresMarket(CandlesChartContainer),
|
||||||
|
menu: CandlesMenu,
|
||||||
},
|
},
|
||||||
depth: {
|
depth: {
|
||||||
label: 'Depth',
|
label: 'Depth',
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const MarketsPage = () => {
|
|||||||
<div className="h-full pt-0.5 pb-3 px-1.5">
|
<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 border-default rounded-sm">
|
||||||
<Tabs storageKey="console-markets">
|
<Tabs storageKey="console-markets">
|
||||||
<Tab id="all-markets" name={t('All markets')}>
|
<Tab id="open-markets" name={t('Open markets')}>
|
||||||
<Markets />
|
<Markets />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="proposed-markets" name={t('Proposed markets')}>
|
<Tab id="proposed-markets" name={t('Proposed markets')}>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const AccountHistoryContainer = () => {
|
|||||||
const { data: assets } = useAssetsDataProvider();
|
const { data: assets } = useAssetsDataProvider();
|
||||||
|
|
||||||
if (!pubKey) {
|
if (!pubKey) {
|
||||||
return <Splash>{t('Connect wallet')}</Splash>;
|
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { DepositsTable } from '@vegaprotocol/deposits';
|
import { DepositsTable } from '@vegaprotocol/deposits';
|
||||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
@@ -17,6 +17,9 @@ export const DepositsContainer = () => {
|
|||||||
skip: !pubKey,
|
skip: !pubKey,
|
||||||
});
|
});
|
||||||
const setView = useSidebar((store) => store.setView);
|
const setView = useSidebar((store) => store.setView);
|
||||||
|
if (!pubKey) {
|
||||||
|
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<DepositsTable
|
<DepositsTable
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { FillsContainer } from '../../components/fills-container';
|
|||||||
import { PositionsContainer } from '../../components/positions-container';
|
import { PositionsContainer } from '../../components/positions-container';
|
||||||
import { WithdrawalsContainer } from './withdrawals-container';
|
import { WithdrawalsContainer } from './withdrawals-container';
|
||||||
import { OrdersContainer } from '../../components/orders-container';
|
import { OrdersContainer } from '../../components/orders-container';
|
||||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
|
||||||
import { LedgerContainer } from '../../components/ledger-container';
|
import { LedgerContainer } from '../../components/ledger-container';
|
||||||
import { AccountHistoryContainer } from './account-history-container';
|
import { AccountHistoryContainer } from './account-history-container';
|
||||||
import {
|
import {
|
||||||
@@ -62,29 +61,19 @@ export const Portfolio = () => {
|
|||||||
<PortfolioGridChild>
|
<PortfolioGridChild>
|
||||||
<Tabs storageKey="console-portfolio-top">
|
<Tabs storageKey="console-portfolio-top">
|
||||||
<Tab id="account-history" name={t('Account history')}>
|
<Tab id="account-history" name={t('Account history')}>
|
||||||
<VegaWalletContainer>
|
<AccountHistoryContainer />
|
||||||
<AccountHistoryContainer />
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="positions" name={t('Positions')}>
|
<Tab id="positions" name={t('Positions')}>
|
||||||
<VegaWalletContainer>
|
<PositionsContainer onMarketClick={onMarketClick} allKeys />
|
||||||
<PositionsContainer onMarketClick={onMarketClick} allKeys />
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="orders" name={t('Orders')}>
|
<Tab id="orders" name={t('Orders')}>
|
||||||
<VegaWalletContainer>
|
<OrdersContainer />
|
||||||
<OrdersContainer />
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="fills" name={t('Fills')}>
|
<Tab id="fills" name={t('Fills')}>
|
||||||
<VegaWalletContainer>
|
<FillsContainer onMarketClick={onMarketClick} />
|
||||||
<FillsContainer onMarketClick={onMarketClick} />
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||||
<VegaWalletContainer>
|
<LedgerContainer />
|
||||||
<LedgerContainer />
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</PortfolioGridChild>
|
</PortfolioGridChild>
|
||||||
@@ -97,14 +86,10 @@ export const Portfolio = () => {
|
|||||||
<PortfolioGridChild>
|
<PortfolioGridChild>
|
||||||
<Tabs storageKey="console-portfolio-bottom">
|
<Tabs storageKey="console-portfolio-bottom">
|
||||||
<Tab id="collateral" name={t('Collateral')}>
|
<Tab id="collateral" name={t('Collateral')}>
|
||||||
<VegaWalletContainer>
|
<AccountsContainer />
|
||||||
<AccountsContainer />
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab id="deposits" name={t('Deposits')}>
|
<Tab id="deposits" name={t('Deposits')}>
|
||||||
<VegaWalletContainer>
|
<DepositsContainer />
|
||||||
<DepositsContainer />
|
|
||||||
</VegaWalletContainer>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
id="withdrawals"
|
id="withdrawals"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import {
|
import {
|
||||||
withdrawalProvider,
|
withdrawalProvider,
|
||||||
WithdrawalsTable,
|
WithdrawalsTable,
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
|
||||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||||
|
|
||||||
export const WithdrawalsContainer = () => {
|
export const WithdrawalsContainer = () => {
|
||||||
@@ -19,9 +18,11 @@ export const WithdrawalsContainer = () => {
|
|||||||
});
|
});
|
||||||
const setView = useSidebar((store) => store.setView);
|
const setView = useSidebar((store) => store.setView);
|
||||||
const { ready, delayed } = useIncompleteWithdrawals();
|
const { ready, delayed } = useIncompleteWithdrawals();
|
||||||
|
if (!pubKey) {
|
||||||
|
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<VegaWalletContainer>
|
<>
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<WithdrawalsTable
|
<WithdrawalsTable
|
||||||
data-testid="withdrawals-history"
|
data-testid="withdrawals-history"
|
||||||
@@ -43,6 +44,6 @@ export const WithdrawalsContainer = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</VegaWalletContainer>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||||
import { AccountManager } from '@vegaprotocol/accounts';
|
import { AccountManager } from '@vegaprotocol/accounts';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
|
||||||
export const THROTTLE_UPDATE_TIME = 500;
|
export const THROTTLE_UPDATE_TIME = 500;
|
||||||
export const RISK_ACCEPTED_KEY = 'vega_risk_accepted';
|
export const ONBOARDING_VIEWED_KEY = 'vega_onboarding_viewed';
|
||||||
export const MAINNET_WELCOME_HEADER = t(
|
export const MAINNET_WELCOME_HEADER = t(
|
||||||
'Trade cash settled futures on the fully decentralised Vega network.'
|
'Trade cash settled futures on the fully decentralised Vega network.'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { FillsManager } from '@vegaprotocol/fills';
|
import { FillsManager } from '@vegaprotocol/fills';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ import {
|
|||||||
NetworkParams,
|
NetworkParams,
|
||||||
useNetworkParams,
|
useNetworkParams,
|
||||||
} from '@vegaprotocol/network-parameters';
|
} from '@vegaprotocol/network-parameters';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
|
||||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
@@ -26,8 +25,6 @@ export const LiquidityContainer = ({
|
|||||||
marketId: string | undefined;
|
marketId: string | undefined;
|
||||||
filter?: Filter;
|
filter?: Filter;
|
||||||
}) => {
|
}) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
|
||||||
|
|
||||||
const gridStore = useLiquidityStore((store) => store.gridStore);
|
const gridStore = useLiquidityStore((store) => store.gridStore);
|
||||||
const updateGridStore = useLiquidityStore((store) => store.updateGridStore);
|
const updateGridStore = useLiquidityStore((store) => store.updateGridStore);
|
||||||
|
|
||||||
@@ -60,7 +57,6 @@ export const LiquidityContainer = ({
|
|||||||
return (
|
return (
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<LiquidityTable
|
<LiquidityTable
|
||||||
ref={gridRef}
|
|
||||||
rowData={data}
|
rowData={data}
|
||||||
symbol={symbol}
|
symbol={symbol}
|
||||||
assetDecimalPlaces={assetDecimalPlaces}
|
assetDecimalPlaces={assetDecimalPlaces}
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ export const Navbar = ({
|
|||||||
'bg-vega-yellow text-vega-clight-50': theme === 'yellow',
|
'bg-vega-yellow text-vega-clight-50': theme === 'yellow',
|
||||||
'text-default': theme === 'system',
|
'text-default': theme === 'system',
|
||||||
})}
|
})}
|
||||||
|
style={{
|
||||||
|
background: theme === 'yellow' ? 'url(/testnet-logo-bg.png' : 'none',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<VLogo className="w-4" />
|
<VLogo className="w-4" />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||||
|
import { Filter, OrderListManager } from '@vegaprotocol/orders';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { Filter } from '@vegaprotocol/orders';
|
|
||||||
import { OrderListManager } from '@vegaprotocol/orders';
|
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import { act, render, screen } from '@testing-library/react';
|
import { act, render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { Sidebar, SidebarContent, ViewType, useSidebar } from './sidebar';
|
import {
|
||||||
|
Sidebar,
|
||||||
|
SidebarButton,
|
||||||
|
SidebarContent,
|
||||||
|
ViewType,
|
||||||
|
useSidebar,
|
||||||
|
} from './sidebar';
|
||||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||||
|
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
jest.mock('../node-health', () => ({
|
jest.mock('../node-health', () => ({
|
||||||
NodeHealthContainer: () => <span data-testid="node-health" />,
|
NodeHealthContainer: () => <span data-testid="node-health" />,
|
||||||
@@ -19,16 +27,23 @@ jest.mock('../settings', () => ({
|
|||||||
Settings: () => <div data-testid="settings" />,
|
Settings: () => <div data-testid="settings" />,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('../welcome-dialog', () => ({
|
||||||
|
GetStarted: () => <div data-testid="get-started" />,
|
||||||
|
}));
|
||||||
|
|
||||||
describe('Sidebar', () => {
|
describe('Sidebar', () => {
|
||||||
it.each(['/markets/all', '/portfolio'])(
|
it.each(['/markets/all', '/portfolio'])(
|
||||||
'does not render ticket and info',
|
'does not render ticket and info',
|
||||||
(path) => {
|
(path) => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter initialEntries={[path]}>
|
<VegaWalletProvider>
|
||||||
<Sidebar />
|
<MemoryRouter initialEntries={[path]}>
|
||||||
</MemoryRouter>
|
<Sidebar />
|
||||||
|
</MemoryRouter>
|
||||||
|
</VegaWalletProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
|
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
|
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
|
||||||
@@ -43,11 +58,14 @@ describe('Sidebar', () => {
|
|||||||
|
|
||||||
it('renders ticket and info on market pages', () => {
|
it('renders ticket and info on market pages', () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
<VegaWalletProvider>
|
||||||
<Sidebar />
|
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||||
</MemoryRouter>
|
<Sidebar />
|
||||||
|
</MemoryRouter>
|
||||||
|
</VegaWalletProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
|
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
|
||||||
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
|
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
|
||||||
@@ -61,9 +79,11 @@ describe('Sidebar', () => {
|
|||||||
|
|
||||||
it('renders selected state', async () => {
|
it('renders selected state', async () => {
|
||||||
render(
|
render(
|
||||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
<VegaWalletProvider>
|
||||||
<Sidebar />
|
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||||
</MemoryRouter>
|
<Sidebar />
|
||||||
|
</MemoryRouter>
|
||||||
|
</VegaWalletProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const settingsButton = screen.getByTestId(ViewType.Settings);
|
const settingsButton = screen.getByTestId(ViewType.Settings);
|
||||||
@@ -87,11 +107,13 @@ describe('Sidebar', () => {
|
|||||||
describe('SidebarContent', () => {
|
describe('SidebarContent', () => {
|
||||||
it('renders the correct content', () => {
|
it('renders the correct content', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
<VegaWalletProvider>
|
||||||
<Routes>
|
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||||
<Route path="/markets/:marketId" element={<SidebarContent />} />
|
<Routes>
|
||||||
</Routes>
|
<Route path="/markets/:marketId" element={<SidebarContent />} />
|
||||||
</MemoryRouter>
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
</VegaWalletProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(container).toBeEmptyDOMElement();
|
expect(container).toBeEmptyDOMElement();
|
||||||
@@ -111,11 +133,13 @@ describe('SidebarContent', () => {
|
|||||||
|
|
||||||
it('closes sidebar if market id is required but not present', () => {
|
it('closes sidebar if market id is required but not present', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<MemoryRouter initialEntries={['/portfolio']}>
|
<VegaWalletProvider>
|
||||||
<Routes>
|
<MemoryRouter initialEntries={['/portfolio']}>
|
||||||
<Route path="/portfolio" element={<SidebarContent />} />
|
<Routes>
|
||||||
</Routes>
|
<Route path="/portfolio" element={<SidebarContent />} />
|
||||||
</MemoryRouter>
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
</VegaWalletProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
@@ -137,3 +161,25 @@ describe('SidebarContent', () => {
|
|||||||
expect(container).toBeEmptyDOMElement();
|
expect(container).toBeEmptyDOMElement();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('SidebarButton', () => {
|
||||||
|
it.each([ViewType.Info, ViewType.Deposit, ViewType.ViewAs])(
|
||||||
|
'runs given callback regardless of requested view (%s)',
|
||||||
|
async (view) => {
|
||||||
|
const onClick = jest.fn();
|
||||||
|
render(
|
||||||
|
<SidebarButton
|
||||||
|
icon={VegaIconNames.INFO}
|
||||||
|
tooltip="INFO"
|
||||||
|
onClick={onClick}
|
||||||
|
view={view}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const btn = screen.getByTestId(view);
|
||||||
|
await userEvent.click(btn);
|
||||||
|
|
||||||
|
expect(onClick).toBeCalled();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { Tooltip } from '../../components/tooltip';
|
|||||||
import { WithdrawContainer } from '../withdraw-container';
|
import { WithdrawContainer } from '../withdraw-container';
|
||||||
import { Routes as AppRoutes } from '../../pages/client-router';
|
import { Routes as AppRoutes } from '../../pages/client-router';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
|
import { GetStarted } from '../welcome-dialog';
|
||||||
|
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
const STORAGE_KEY = 'vega_sidebar_store';
|
const STORAGE_KEY = 'vega_sidebar_store';
|
||||||
|
|
||||||
@@ -25,6 +27,7 @@ export enum ViewType {
|
|||||||
Withdraw = 'Withdraw',
|
Withdraw = 'Withdraw',
|
||||||
Transfer = 'Transfer',
|
Transfer = 'Transfer',
|
||||||
Settings = 'Settings',
|
Settings = 'Settings',
|
||||||
|
ViewAs = 'ViewAs',
|
||||||
}
|
}
|
||||||
|
|
||||||
type SidebarView =
|
type SidebarView =
|
||||||
@@ -52,6 +55,8 @@ type SidebarView =
|
|||||||
|
|
||||||
export const Sidebar = () => {
|
export const Sidebar = () => {
|
||||||
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
|
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
|
||||||
|
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
|
||||||
|
const { pubKeys } = useVegaWallet();
|
||||||
return (
|
return (
|
||||||
<div className="flex lg:flex-col gap-2 h-full p-1" data-testid="sidebar">
|
<div className="flex lg:flex-col gap-2 h-full p-1" data-testid="sidebar">
|
||||||
<nav className={navClasses}>
|
<nav className={navClasses}>
|
||||||
@@ -104,6 +109,16 @@ export const Sidebar = () => {
|
|||||||
</Routes>
|
</Routes>
|
||||||
</nav>
|
</nav>
|
||||||
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
|
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
|
||||||
|
<SidebarButton
|
||||||
|
view={ViewType.ViewAs}
|
||||||
|
onClick={() => {
|
||||||
|
setViewAsDialogOpen(true);
|
||||||
|
}}
|
||||||
|
icon={VegaIconNames.EYE}
|
||||||
|
tooltip={t('View as party')}
|
||||||
|
disabled={Boolean(pubKeys)}
|
||||||
|
/>
|
||||||
|
|
||||||
<SidebarButton
|
<SidebarButton
|
||||||
view={ViewType.Settings}
|
view={ViewType.Settings}
|
||||||
icon={VegaIconNames.COG}
|
icon={VegaIconNames.COG}
|
||||||
@@ -115,25 +130,43 @@ export const Sidebar = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarButton = ({
|
export const SidebarButton = ({
|
||||||
view,
|
view,
|
||||||
icon,
|
icon,
|
||||||
tooltip,
|
tooltip,
|
||||||
|
disabled = false,
|
||||||
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
view: ViewType;
|
view?: ViewType;
|
||||||
icon: VegaIconNames;
|
icon: VegaIconNames;
|
||||||
tooltip: string;
|
tooltip: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { currView, setView } = useSidebar((store) => ({
|
const { currView, setView } = useSidebar((store) => ({
|
||||||
currView: store.view,
|
currView: store.view,
|
||||||
setView: store.setView,
|
setView: store.setView,
|
||||||
}));
|
}));
|
||||||
const buttonClasses = classNames('flex items-center p-1 rounded', {
|
|
||||||
'text-vega-clight-200 dark:text-vega-cdark-200 hover:bg-vega-clight-500 dark:hover:bg-vega-cdark-500':
|
const onSelect = (view: SidebarView['type']) => {
|
||||||
view !== currView?.type,
|
if (view === currView?.type) {
|
||||||
'bg-vega-yellow hover:bg-vega-yellow-550 text-black':
|
setView(null);
|
||||||
view === currView?.type,
|
} else {
|
||||||
});
|
setView({ type: view });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonClasses = classNames(
|
||||||
|
'flex items-center p-1 rounded',
|
||||||
|
'disabled:cursor-not-allowed disabled:text-vega-clight-500 dark:disabled:text-vega-cdark-500',
|
||||||
|
{
|
||||||
|
'text-vega-clight-200 dark:text-vega-cdark-200 enabled:hover:bg-vega-clight-500 dark:enabled:hover:bg-vega-cdark-500':
|
||||||
|
!view || view !== currView?.type,
|
||||||
|
'bg-vega-yellow enabled:hover:bg-vega-yellow-550 text-black':
|
||||||
|
view && view === currView?.type,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
description={tooltip}
|
description={tooltip}
|
||||||
@@ -145,13 +178,8 @@ const SidebarButton = ({
|
|||||||
<button
|
<button
|
||||||
className={buttonClasses}
|
className={buttonClasses}
|
||||||
data-testid={view}
|
data-testid={view}
|
||||||
onClick={() => {
|
onClick={onClick || (() => onSelect(view as SidebarView['type']))}
|
||||||
if (view === currView?.type) {
|
disabled={disabled}
|
||||||
setView(null);
|
|
||||||
} else {
|
|
||||||
setView({ type: view });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<VegaIcon name={icon} size={20} />
|
<VegaIcon name={icon} size={20} />
|
||||||
</button>
|
</button>
|
||||||
@@ -184,6 +212,7 @@ export const SidebarContent = () => {
|
|||||||
setView({ type: ViewType.Deposit, assetId })
|
setView({ type: ViewType.Deposit, assetId })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<GetStarted />
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -207,6 +236,7 @@ export const SidebarContent = () => {
|
|||||||
return (
|
return (
|
||||||
<ContentWrapper title={t('Deposit')}>
|
<ContentWrapper title={t('Deposit')}>
|
||||||
<DepositContainer assetId={view.assetId} />
|
<DepositContainer assetId={view.assetId} />
|
||||||
|
<GetStarted />
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -215,6 +245,7 @@ export const SidebarContent = () => {
|
|||||||
return (
|
return (
|
||||||
<ContentWrapper title={t('Withdraw')}>
|
<ContentWrapper title={t('Withdraw')}>
|
||||||
<WithdrawContainer assetId={view.assetId} />
|
<WithdrawContainer assetId={view.assetId} />
|
||||||
|
<GetStarted />
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -223,6 +254,7 @@ export const SidebarContent = () => {
|
|||||||
return (
|
return (
|
||||||
<ContentWrapper title={t('Transfer')}>
|
<ContentWrapper title={t('Transfer')}>
|
||||||
<TransferContainer assetId={view.assetId} />
|
<TransferContainer assetId={view.assetId} />
|
||||||
|
<GetStarted />
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -277,7 +309,7 @@ export const useSidebar = create<{
|
|||||||
view: null,
|
view: null,
|
||||||
setView: (x) =>
|
setView: (x) =>
|
||||||
set(() => {
|
set(() => {
|
||||||
if (x === null) {
|
if (x == null) {
|
||||||
return { view: null, init: false };
|
return { view: null, init: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
@@ -27,6 +27,16 @@ describe('VegaWalletConnectButton', () => {
|
|||||||
it('should fire dialog when not connected', () => {
|
it('should fire dialog when not connected', () => {
|
||||||
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
||||||
|
|
||||||
|
const button = screen.getByTestId('connect-vega-wallet');
|
||||||
|
expect(button).toHaveTextContent('Get started');
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(mockUpdateDialogOpen).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render "Connect" when browser wallet is detected', () => {
|
||||||
|
window.vega = window.vega || ({} as Vega);
|
||||||
|
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
||||||
|
|
||||||
const button = screen.getByTestId('connect-vega-wallet');
|
const button = screen.getByTestId('connect-vega-wallet');
|
||||||
expect(button).toHaveTextContent('Connect');
|
expect(button).toHaveTextContent('Connect');
|
||||||
fireEvent.click(button);
|
fireEvent.click(button);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
|
import { isBrowserWalletInstalled } from '@vegaprotocol/wallet';
|
||||||
import { truncateByChars } from '@vegaprotocol/utils';
|
import { truncateByChars } from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import {
|
import {
|
||||||
@@ -37,7 +38,7 @@ export const VegaWalletConnectButton = () => {
|
|||||||
fetchPubKeys,
|
fetchPubKeys,
|
||||||
} = useVegaWallet();
|
} = useVegaWallet();
|
||||||
const isConnected = pubKey !== null;
|
const isConnected = pubKey !== null;
|
||||||
|
const walletInstalled = isBrowserWalletInstalled();
|
||||||
const activeKey = useMemo(() => {
|
const activeKey = useMemo(() => {
|
||||||
return pubKeys?.find((pk) => pk.publicKey === pubKey);
|
return pubKeys?.find((pk) => pk.publicKey === pubKey);
|
||||||
}, [pubKey, pubKeys]);
|
}, [pubKey, pubKeys]);
|
||||||
@@ -118,7 +119,9 @@ export const VegaWalletConnectButton = () => {
|
|||||||
intent={Intent.None}
|
intent={Intent.None}
|
||||||
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
|
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
|
||||||
>
|
>
|
||||||
<span className="whitespace-nowrap uppercase">{t('Connect')}</span>
|
<span className="whitespace-nowrap uppercase">
|
||||||
|
{walletInstalled ? t('Connect') : t('Get started')}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { ExternalLink, Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import {
|
||||||
|
useVegaWallet,
|
||||||
|
useVegaWalletDialogStore,
|
||||||
|
isBrowserWalletInstalled,
|
||||||
|
} from '@vegaprotocol/wallet';
|
||||||
|
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||||
|
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||||
|
import * as constants from '../constants';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
lead?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GetStarted = ({ lead }: Props) => {
|
||||||
|
const { pubKey } = useVegaWallet();
|
||||||
|
|
||||||
|
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
|
||||||
|
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
|
||||||
|
|
||||||
|
const [, setOnboardingViewed] = useLocalStorage(
|
||||||
|
constants.ONBOARDING_VIEWED_KEY
|
||||||
|
);
|
||||||
|
|
||||||
|
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||||
|
(store) => store.openVegaWalletDialog
|
||||||
|
);
|
||||||
|
|
||||||
|
const onButtonClick = () => {
|
||||||
|
openVegaWalletDialog();
|
||||||
|
setOnboardingViewed('true');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!pubKey && !isBrowserWalletInstalled()) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'flex flex-col bg-vega-blue-300 dark:bg-vega-blue-700 border border-vega-blue-350 dark:border-vega-blue-650 px-6 gap-4',
|
||||||
|
{ 'py-4': lead },
|
||||||
|
{ 'mt-8 py-6': !lead }
|
||||||
|
)}
|
||||||
|
data-testid="get-started-banner"
|
||||||
|
>
|
||||||
|
{lead && <h2>{lead}</h2>}
|
||||||
|
<h3>{t('Get started')}</h3>
|
||||||
|
<div>
|
||||||
|
<ul className="list-decimal list-inside">
|
||||||
|
<li>{t('Get a Vega wallet')}</li>
|
||||||
|
<li>{t('Connect')}</li>
|
||||||
|
<li>{t('Deposit funds')}</li>
|
||||||
|
<li>{t('Open a position')}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TradingButton
|
||||||
|
intent={Intent.Info}
|
||||||
|
onClick={onButtonClick}
|
||||||
|
className="block w-full"
|
||||||
|
data-testid="get-started-button"
|
||||||
|
>
|
||||||
|
{t('Get started')}
|
||||||
|
</TradingButton>
|
||||||
|
</div>
|
||||||
|
{VEGA_ENV === Networks.MAINNET && (
|
||||||
|
<p className="text-sm">
|
||||||
|
{t('Experiment for free with virtual assets on')}{' '}
|
||||||
|
<ExternalLink href={CANONICAL_URL}>
|
||||||
|
{t('Fairground Testnet')}
|
||||||
|
</ExternalLink>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{VEGA_ENV === Networks.TESTNET && (
|
||||||
|
<p className="text-sm">
|
||||||
|
{t('Ready to trade with real funds?')}{' '}
|
||||||
|
<ExternalLink href={CANONICAL_URL}>
|
||||||
|
{t('Switch to Mainnet')}
|
||||||
|
</ExternalLink>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './welcome-dialog';
|
export * from './welcome-dialog';
|
||||||
export * from './risk-message';
|
export * from './risk-message';
|
||||||
|
export * from './get-started';
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
import { render, screen, fireEvent } from '@testing-library/react';
|
|
||||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
|
||||||
import { RiskNoticeDialog } from './risk-notice-dialog';
|
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/environment');
|
|
||||||
|
|
||||||
const mockEnvDefinitions = {
|
|
||||||
VEGA_CONFIG_URL: 'https://config.url',
|
|
||||||
VEGA_URL: 'https://test.url',
|
|
||||||
VEGA_NETWORKS: JSON.stringify({}),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Risk notice dialog', () => {
|
|
||||||
const mockOnClose = jest.fn();
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each`
|
|
||||||
assertion | network
|
|
||||||
${'displays'} | ${Networks.CUSTOM}
|
|
||||||
${'displays'} | ${Networks.DEVNET}
|
|
||||||
${'displays'} | ${Networks.TESTNET}
|
|
||||||
`(
|
|
||||||
'$assertion a generic message on $network',
|
|
||||||
async ({ assertion, network }) => {
|
|
||||||
// @ts-ignore ignore mock implementation
|
|
||||||
useEnvironment.mockImplementation(() => ({
|
|
||||||
...mockEnvDefinitions,
|
|
||||||
VEGA_ENV: network,
|
|
||||||
}));
|
|
||||||
|
|
||||||
render(<RiskNoticeDialog onClose={mockOnClose} network={network} />);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
screen.getByText(
|
|
||||||
new RegExp(
|
|
||||||
`This application for trading on Vega is connected to ${network}`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
).toBeInTheDocument();
|
|
||||||
|
|
||||||
const button = screen.getByRole('button', {
|
|
||||||
name: 'Continue',
|
|
||||||
});
|
|
||||||
fireEvent.click(button);
|
|
||||||
expect(mockOnClose).toHaveBeenCalled();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Link,
|
|
||||||
VegaIcon,
|
|
||||||
VegaIconNames,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { RISK_ACCEPTED_KEY } from '../constants';
|
|
||||||
import { TelemetryApproval } from './telemetry-approval';
|
|
||||||
import type { Networks } from '@vegaprotocol/environment';
|
|
||||||
import {
|
|
||||||
useEnvironment,
|
|
||||||
DocsLinks,
|
|
||||||
ExternalLinks,
|
|
||||||
} from '@vegaprotocol/environment';
|
|
||||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
onClose: () => void;
|
|
||||||
network: Networks;
|
|
||||||
}
|
|
||||||
export const RiskNoticeDialog = ({ onClose, network }: Props) => {
|
|
||||||
const [, setValue] = useLocalStorage(RISK_ACCEPTED_KEY);
|
|
||||||
|
|
||||||
const handleAcceptRisk = () => {
|
|
||||||
onClose();
|
|
||||||
setValue('true');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TestnetContent network={network} handleAcceptRisk={handleAcceptRisk} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TestnetContent = ({
|
|
||||||
network,
|
|
||||||
handleAcceptRisk,
|
|
||||||
}: {
|
|
||||||
network: Networks;
|
|
||||||
handleAcceptRisk: () => void;
|
|
||||||
}) => {
|
|
||||||
const { GITHUB_FEEDBACK_URL } = useEnvironment();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p className="mb-4">
|
|
||||||
{t(
|
|
||||||
'This application for trading on Vega is connected to %s, meaning you are free to try out trading with virtual assets and no risk.',
|
|
||||||
[network]
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="mb-4">
|
|
||||||
{t(
|
|
||||||
'Your Vega wallet must also be connected to %s, and your Ethereum wallet must be connected to Sepolia.',
|
|
||||||
[network]
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{GITHUB_FEEDBACK_URL && DocsLinks && (
|
|
||||||
<ul className="list-disc pl-4">
|
|
||||||
<li className="mb-1">
|
|
||||||
<Link href={ExternalLinks.VEGA_WALLET_URL} target="_blank">
|
|
||||||
<span className="underline">{t('Get a Vega Wallet')}</span>{' '}
|
|
||||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li className="mb-1">
|
|
||||||
<Link href={DocsLinks.VEGA_WALLET_TOOLS_URL} target="_blank">
|
|
||||||
<span className="underline">{t('Learn about Vega Wallet')}</span>{' '}
|
|
||||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li className="mb-1">
|
|
||||||
<Link href={GITHUB_FEEDBACK_URL} target="_blank">
|
|
||||||
<span className="underline">{t('Provide feedback')}</span>{' '}
|
|
||||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
<div className="my-4">
|
|
||||||
<TelemetryApproval
|
|
||||||
helpText={t(
|
|
||||||
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button onClick={handleAcceptRisk}>{t('Continue')}</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { GetStarted } from './get-started';
|
||||||
|
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Links, Routes } from '../../pages/client-router';
|
||||||
|
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||||
|
import * as constants from '../constants';
|
||||||
|
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export const WelcomeDialogContent = () => {
|
||||||
|
const { VEGA_ENV } = useEnvironment();
|
||||||
|
const [, setOnboardingViewed] = useLocalStorage(
|
||||||
|
constants.ONBOARDING_VIEWED_KEY
|
||||||
|
);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const browseMarkets = () => {
|
||||||
|
const link = Links[Routes.MARKETS]();
|
||||||
|
navigate(link);
|
||||||
|
setOnboardingViewed('true');
|
||||||
|
};
|
||||||
|
const lead =
|
||||||
|
VEGA_ENV === Networks.MAINNET
|
||||||
|
? t('Start trading on the worlds most advanced decentralised exchange.')
|
||||||
|
: t(
|
||||||
|
'Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.'
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col sm:flex-row gap-8">
|
||||||
|
<div className="sm:w-1/2 flex flex-col justify-between pt-3">
|
||||||
|
<ul className="ml-0">
|
||||||
|
<ListItemContent
|
||||||
|
icon={<NonCustodialIcon />}
|
||||||
|
title={t('Non-cutodial and pseudonymous')}
|
||||||
|
text={t('No third party has access to your funds.')}
|
||||||
|
/>
|
||||||
|
<ListItemContent
|
||||||
|
icon={<PurposeBuiltIcon />}
|
||||||
|
title={t('Purpose built proof of stake blockhain')}
|
||||||
|
text={t(
|
||||||
|
'Fully decentralised high performance peer-to-network trading.'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<ListItemContent
|
||||||
|
icon={<RewardIcon />}
|
||||||
|
title={t('Low fees and no cost to place orders')}
|
||||||
|
text={t(
|
||||||
|
'Fees work like a CEX with no per-transaction gas for orders'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
<TradingButton
|
||||||
|
onClick={browseMarkets}
|
||||||
|
className="block w-full"
|
||||||
|
data-testid="browse-markets-button"
|
||||||
|
>
|
||||||
|
{t('Browse the markets')}
|
||||||
|
</TradingButton>
|
||||||
|
</div>
|
||||||
|
<div className="sm:w-1/2">
|
||||||
|
<GetStarted lead={lead} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListItemContent = ({
|
||||||
|
icon,
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
}: {
|
||||||
|
icon: ReactNode;
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<li className="my-4 flex gap-3">
|
||||||
|
<div className="shrink-0 pt-1">{icon}</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg leading-snug mb-2">{title}</h3>
|
||||||
|
<p className="text-sm text-secondary">{text}</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PurposeBuiltIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg width="40" height="40" viewBox="0 0 30 30" fill="currentColor">
|
||||||
|
<rect x="14" y="20" width="2" height="2" />
|
||||||
|
<rect x="12" y="18" width="2" height="2" />
|
||||||
|
<rect x="10" y="8" width="2" height="10" />
|
||||||
|
<rect x="16" y="18" width="2" height="2" />
|
||||||
|
<rect x="20" y="16" width="2" height="2" />
|
||||||
|
<rect x="18" y="8" width="2" height="8" />
|
||||||
|
<rect x="28" y="2" width="2" height="26" />
|
||||||
|
<rect y="2" width="2" height="26" />
|
||||||
|
<rect x="28" width="2" height="26" transform="rotate(90 28 0)" />
|
||||||
|
<rect x="28" y="28" width="2" height="26" transform="rotate(90 28 28)" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NonCustodialIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg width="40" height="40" viewBox="0 0 30 30" className="fill-current">
|
||||||
|
<rect x="8" y="22" width="14" height="2" />
|
||||||
|
<rect x="8" y="12" width="14" height="2" />
|
||||||
|
<rect
|
||||||
|
x="22"
|
||||||
|
y="24"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
transform="rotate(-180 22 24)"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="10"
|
||||||
|
y="24"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
transform="rotate(-180 10 24)"
|
||||||
|
/>
|
||||||
|
<rect x="12" y="12" width="2" height="2" transform="rotate(-180 12 12)" />
|
||||||
|
<rect x="20" y="12" width="2" height="2" transform="rotate(-180 20 12)" />
|
||||||
|
<rect x="12" y="10" width="2" height="2" transform="rotate(-180 12 10)" />
|
||||||
|
<rect x="20" y="10" width="2" height="2" transform="rotate(-180 20 10)" />
|
||||||
|
<rect x="18" y="8" width="2" height="2" transform="rotate(-180 18 8)" />
|
||||||
|
<rect x="14" y="8" width="2" height="2" transform="rotate(-180 14 8)" />
|
||||||
|
<rect x="16" y="8" width="2" height="2" transform="rotate(-180 16 8)" />
|
||||||
|
<rect x="28" y="2" width="2" height="26" />
|
||||||
|
<rect y="2" width="2" height="26" />
|
||||||
|
<rect x="28" width="2" height="26" transform="rotate(90 28 0)" />
|
||||||
|
<rect x="28" y="28" width="2" height="26" transform="rotate(90 28 28)" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const RewardIcon = () => {
|
||||||
|
return (
|
||||||
|
<svg width="40" height="40" viewBox="0 0 30 30" fill="currentColor">
|
||||||
|
<rect x="6" y="16" width="4" height="2" />
|
||||||
|
<rect x="10" y="14" width="2" height="2" />
|
||||||
|
<rect x="12" y="24" width="4" height="2" transform="rotate(-90 12 24)" />
|
||||||
|
<rect x="10" y="20" width="2" height="2" transform="rotate(-90 10 20)" />
|
||||||
|
<rect x="20" y="18" width="4" height="2" transform="rotate(-180 20 18)" />
|
||||||
|
<rect x="16" y="20" width="2" height="2" transform="rotate(-180 16 20)" />
|
||||||
|
<rect x="16" y="14" width="2" height="2" transform="rotate(90 16 14)" />
|
||||||
|
<rect x="22" y="10" width="2" height="2" transform="rotate(90 22 10)" />
|
||||||
|
<rect x="20" y="8" width="2" height="2" transform="rotate(90 20 8)" />
|
||||||
|
<rect x="24" y="8" width="2" height="2" transform="rotate(90 24 8)" />
|
||||||
|
<rect x="22" y="6" width="2" height="2" transform="rotate(90 22 6)" />
|
||||||
|
<rect x="12" y="10" width="2" height="4" />
|
||||||
|
<rect x="28" y="2" width="2" height="26" />
|
||||||
|
<rect y="2" width="2" height="26" />
|
||||||
|
<rect x="28" width="2" height="26" transform="rotate(90 28 0)" />
|
||||||
|
<rect x="28" y="28" width="2" height="26" transform="rotate(90 28 28)" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,68 +1,52 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Dialog } from '@vegaprotocol/ui-toolkit';
|
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import { activeMarketsProvider } from '@vegaprotocol/markets';
|
|
||||||
import * as constants from '../constants';
|
|
||||||
import { RiskNoticeDialog } from './risk-notice-dialog';
|
|
||||||
import { WelcomeNoticeDialog } from './welcome-notice-dialog';
|
|
||||||
import { useGlobalStore } from '../../stores';
|
|
||||||
import { useEnvironment } from '@vegaprotocol/environment';
|
import { useEnvironment } from '@vegaprotocol/environment';
|
||||||
import { Networks } from '@vegaprotocol/environment';
|
import { isBrowserWalletInstalled } from '@vegaprotocol/wallet';
|
||||||
import { isTestEnv } from '@vegaprotocol/utils';
|
import * as constants from '../constants';
|
||||||
|
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||||
|
import { getConfig } from '@vegaprotocol/wallet';
|
||||||
|
import { Links, Routes } from '../../pages/client-router';
|
||||||
|
import { useGlobalStore } from '../../stores';
|
||||||
|
|
||||||
export const WelcomeDialog = () => {
|
export const WelcomeDialog = () => {
|
||||||
const { VEGA_ENV } = useEnvironment();
|
const { VEGA_ENV } = useEnvironment();
|
||||||
const { pathname } = useLocation();
|
const [onBoardingViewed, setOnboardingViewed] = useLocalStorage(
|
||||||
let dialogContent: React.ReactNode;
|
constants.ONBOARDING_VIEWED_KEY
|
||||||
let title = '';
|
);
|
||||||
let size: 'small' | 'medium' = 'small';
|
const navigate = useNavigate();
|
||||||
let onClose: ((open: boolean) => void) | undefined = undefined;
|
const isOnboardingDialogNeeded =
|
||||||
const [riskAccepted] = useLocalStorage(constants.RISK_ACCEPTED_KEY);
|
onBoardingViewed !== 'true' && !isBrowserWalletInstalled() && !getConfig();
|
||||||
const { data } = useDataProvider({
|
const marketId = useGlobalStore((store) => store.marketId);
|
||||||
dataProvider: activeMarketsProvider,
|
|
||||||
variables: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const update = useGlobalStore((store) => store.update);
|
const onClose = () => {
|
||||||
const shouldDisplayWelcomeDialog = useGlobalStore(
|
setOnboardingViewed('true');
|
||||||
(store) => store.shouldDisplayWelcomeDialog
|
const link = marketId
|
||||||
|
? Links[Routes.MARKET](marketId)
|
||||||
|
: Links[Routes.HOME]();
|
||||||
|
navigate(link);
|
||||||
|
};
|
||||||
|
const title = (
|
||||||
|
<span className="font-alpha calt" data-testid="welcome-title">
|
||||||
|
{t('Console')}{' '}
|
||||||
|
<span className="text-vega-clight-100 dark:text-vega-cdark-100">
|
||||||
|
{VEGA_ENV}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
const isRiskDialogNeeded =
|
return isOnboardingDialogNeeded ? (
|
||||||
riskAccepted !== 'true' && VEGA_ENV !== Networks.MAINNET && !isTestEnv();
|
|
||||||
|
|
||||||
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
|
|
||||||
|
|
||||||
const onCloseDialog = useCallback(() => {
|
|
||||||
update({
|
|
||||||
shouldDisplayWelcomeDialog: isRiskDialogNeeded,
|
|
||||||
});
|
|
||||||
}, [update, isRiskDialogNeeded]);
|
|
||||||
|
|
||||||
if (isRiskDialogNeeded) {
|
|
||||||
dialogContent = (
|
|
||||||
<RiskNoticeDialog onClose={onCloseDialog} network={VEGA_ENV} />
|
|
||||||
);
|
|
||||||
title = t('Vega Console');
|
|
||||||
size = 'medium';
|
|
||||||
} else if (isWelcomeDialogNeeded && data?.length === 0) {
|
|
||||||
dialogContent = <WelcomeNoticeDialog />;
|
|
||||||
onClose = onCloseDialog;
|
|
||||||
} else {
|
|
||||||
dialogContent = null as React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog
|
<Dialog
|
||||||
open={Boolean(dialogContent)}
|
open
|
||||||
title={title}
|
title={title}
|
||||||
size={size}
|
size="medium"
|
||||||
onChange={onClose}
|
onChange={onClose}
|
||||||
|
intent={Intent.None}
|
||||||
|
dataTestId="welcome-dialog"
|
||||||
>
|
>
|
||||||
{dialogContent}
|
<WelcomeDialogContent />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
) : null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import {
|
|
||||||
DApp,
|
|
||||||
Networks,
|
|
||||||
TOKEN_NEW_MARKET_PROPOSAL,
|
|
||||||
TOKEN_PROPOSALS,
|
|
||||||
useEnvironment,
|
|
||||||
useLinks,
|
|
||||||
ExternalLinks,
|
|
||||||
} from '@vegaprotocol/environment';
|
|
||||||
import { ProposedMarkets } from './proposed-markets';
|
|
||||||
import { TelemetryApproval } from './telemetry-approval';
|
|
||||||
|
|
||||||
export const WelcomeNoticeDialog = () => {
|
|
||||||
const { VEGA_ENV } = useEnvironment();
|
|
||||||
const tokenLink = useLinks(DApp.Token);
|
|
||||||
const consoleFairgroundLink = useLinks(DApp.Console, Networks.TESTNET);
|
|
||||||
const isMainnet = VEGA_ENV === Networks.MAINNET;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h1
|
|
||||||
data-testid="welcome-notice-title"
|
|
||||||
className="text-2xl uppercase mb-6 text-center font-alpha calt"
|
|
||||||
>
|
|
||||||
{t('Welcome to Console')}
|
|
||||||
</h1>
|
|
||||||
<p className="leading-6 mb-4">
|
|
||||||
{t(
|
|
||||||
'There are no markets to trade on right now. Trading on Vega is now live, but markets need to pass a governance vote before they can be traded on. In the meantime:'
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<ul className="list-[square] pl-4 mb-4">
|
|
||||||
{isMainnet && (
|
|
||||||
<li className="mb-1">
|
|
||||||
<ExternalLink target="_blank" href={consoleFairgroundLink()}>
|
|
||||||
{t('Try out Console')}
|
|
||||||
</ExternalLink>
|
|
||||||
{t(' on Fairground, our Testnet')}
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
<li className="mb-1">
|
|
||||||
<ExternalLink target="_blank" href={tokenLink(TOKEN_PROPOSALS)}>
|
|
||||||
{t('View and vote for proposed markets')}
|
|
||||||
</ExternalLink>
|
|
||||||
</li>
|
|
||||||
<li className="mb-1">
|
|
||||||
<ExternalLink
|
|
||||||
target="_blank"
|
|
||||||
href={tokenLink(TOKEN_NEW_MARKET_PROPOSAL)}
|
|
||||||
>
|
|
||||||
{t('Propose a market')}
|
|
||||||
</ExternalLink>
|
|
||||||
</li>
|
|
||||||
<li className="mb-1">
|
|
||||||
<ExternalLink target="_blank" href={ExternalLinks.BLOG}>
|
|
||||||
{t('Read about the mainnet launch')}
|
|
||||||
</ExternalLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{isMainnet && (
|
|
||||||
<TelemetryApproval
|
|
||||||
helpText={t(
|
|
||||||
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<ProposedMarkets />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -31,6 +31,10 @@ const LazyDisclaimer = dynamic(() => import('../client-pages/disclaimer'), {
|
|||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const LazyDeposit = dynamic(() => import('../client-pages/deposit'), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
export enum Routes {
|
export enum Routes {
|
||||||
HOME = '/',
|
HOME = '/',
|
||||||
MARKET = '/markets/:marketId',
|
MARKET = '/markets/:marketId',
|
||||||
@@ -38,6 +42,7 @@ export enum Routes {
|
|||||||
PORTFOLIO = '/portfolio',
|
PORTFOLIO = '/portfolio',
|
||||||
LIQUIDITY = '/liquidity/:marketId',
|
LIQUIDITY = '/liquidity/:marketId',
|
||||||
DISCLAIMER = '/disclaimer',
|
DISCLAIMER = '/disclaimer',
|
||||||
|
DEPOSIT = '/deposit',
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConsoleLinks = { [r in Routes]: (...args: string[]) => string };
|
type ConsoleLinks = { [r in Routes]: (...args: string[]) => string };
|
||||||
@@ -51,6 +56,7 @@ export const Links: ConsoleLinks = {
|
|||||||
[Routes.LIQUIDITY]: (marketId: string) =>
|
[Routes.LIQUIDITY]: (marketId: string) =>
|
||||||
trimEnd(Routes.LIQUIDITY.replace(':marketId', marketId)),
|
trimEnd(Routes.LIQUIDITY.replace(':marketId', marketId)),
|
||||||
[Routes.DISCLAIMER]: () => Routes.DISCLAIMER,
|
[Routes.DISCLAIMER]: () => Routes.DISCLAIMER,
|
||||||
|
[Routes.DEPOSIT]: () => Routes.DEPOSIT,
|
||||||
};
|
};
|
||||||
|
|
||||||
const routerConfig: RouteObject[] = [
|
const routerConfig: RouteObject[] = [
|
||||||
@@ -97,6 +103,7 @@ const routerConfig: RouteObject[] = [
|
|||||||
path: Routes.DISCLAIMER,
|
path: Routes.DISCLAIMER,
|
||||||
element: <LazyDisclaimer />,
|
element: <LazyDisclaimer />,
|
||||||
},
|
},
|
||||||
|
{ path: Routes.DEPOSIT, element: <LazyDeposit /> },
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
element: (
|
element: (
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
AssetDetailsDialog,
|
AssetDetailsDialog,
|
||||||
useAssetDetailsDialogStore,
|
useAssetDetailsDialogStore,
|
||||||
} from '@vegaprotocol/assets';
|
} from '@vegaprotocol/assets';
|
||||||
import { VegaConnectDialog } from '@vegaprotocol/wallet';
|
import { VegaConnectDialog, ViewAsDialog } from '@vegaprotocol/wallet';
|
||||||
import { Connectors } from '../lib/vega-connectors';
|
import { Connectors } from '../lib/vega-connectors';
|
||||||
import {
|
import {
|
||||||
Web3ConnectUncontrolledDialog,
|
Web3ConnectUncontrolledDialog,
|
||||||
@@ -19,6 +19,7 @@ const DialogsContainer = () => {
|
|||||||
connectors={Connectors}
|
connectors={Connectors}
|
||||||
riskMessage={<RiskMessage />}
|
riskMessage={<RiskMessage />}
|
||||||
/>
|
/>
|
||||||
|
<ViewAsDialog connector={Connectors.view} />
|
||||||
<AssetDetailsDialog
|
<AssetDetailsDialog
|
||||||
assetId={id}
|
assetId={id}
|
||||||
trigger={trigger || null}
|
trigger={trigger || null}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -5,7 +5,6 @@ import produce from 'immer';
|
|||||||
interface GlobalStore {
|
interface GlobalStore {
|
||||||
marketId: string | null;
|
marketId: string | null;
|
||||||
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
|
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
|
||||||
shouldDisplayWelcomeDialog: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PageTitleStore {
|
interface PageTitleStore {
|
||||||
@@ -15,7 +14,6 @@ interface PageTitleStore {
|
|||||||
|
|
||||||
export const useGlobalStore = create<GlobalStore>()((set) => ({
|
export const useGlobalStore = create<GlobalStore>()((set) => ({
|
||||||
marketId: LocalStorage.getItem('marketId') || null,
|
marketId: LocalStorage.getItem('marketId') || null,
|
||||||
shouldDisplayWelcomeDialog: false,
|
|
||||||
update: (newState) => {
|
update: (newState) => {
|
||||||
set(
|
set(
|
||||||
produce((state: GlobalStore) => {
|
produce((state: GlobalStore) => {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export const AccountBreakdownDialog = memo(
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
size="medium"
|
size="large"
|
||||||
open={Boolean(assetId)}
|
open={Boolean(assetId)}
|
||||||
onChange={(isOpen) => {
|
onChange={(isOpen) => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
import { CandlesChartContainer } from './candles-chart';
|
|
||||||
import { render, screen, waitFor, act } from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
|
||||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
|
||||||
import { CandlesEventsDocument } from './__generated__/Candles';
|
|
||||||
import type { CandlesEventsSubscription } from './__generated__/Candles';
|
|
||||||
|
|
||||||
const candles: CandlesEventsSubscription = {
|
|
||||||
candles: {
|
|
||||||
lastUpdateInPeriod: 0,
|
|
||||||
periodStart: 0,
|
|
||||||
open: '0',
|
|
||||||
high: '0',
|
|
||||||
low: '0',
|
|
||||||
close: '0',
|
|
||||||
volume: '0',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const mocks = [
|
|
||||||
{
|
|
||||||
request: {
|
|
||||||
query: CandlesEventsDocument,
|
|
||||||
variables: { marketId: 'market-id', interval: 'INTERVAL_I15M' },
|
|
||||||
},
|
|
||||||
result: { data: candles },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
describe('TradingChart', () => {
|
|
||||||
it('should render successfully', () => {
|
|
||||||
const { baseElement } = render(
|
|
||||||
<MockedProvider>
|
|
||||||
<VegaWalletContext.Provider value={{} as never}>
|
|
||||||
<CandlesChartContainer marketId={'market-id'} />
|
|
||||||
</VegaWalletContext.Provider>
|
|
||||||
</MockedProvider>
|
|
||||||
);
|
|
||||||
expect(baseElement).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('volume study should be preselected', async () => {
|
|
||||||
act(() => {
|
|
||||||
render(
|
|
||||||
<MockedProvider mocks={mocks}>
|
|
||||||
<VegaWalletContext.Provider value={{} as never}>
|
|
||||||
<CandlesChartContainer marketId={'market-id'} />
|
|
||||||
</VegaWalletContext.Provider>
|
|
||||||
</MockedProvider>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(
|
|
||||||
screen.getByText('Studies', {
|
|
||||||
selector: '[type="button"]',
|
|
||||||
})
|
|
||||||
).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
act(() => {
|
|
||||||
userEvent.click(
|
|
||||||
screen.getByText('Studies', {
|
|
||||||
selector: '[type="button"]',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByRole('menu')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
expect(screen.getByText('Volume')).toHaveAttribute('data-state', 'checked');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,92 +1,12 @@
|
|||||||
import 'pennant/dist/style.css';
|
import 'pennant/dist/style.css';
|
||||||
import {
|
import { CandlestickChart } from 'pennant';
|
||||||
CandlestickChart,
|
|
||||||
ChartType,
|
|
||||||
Interval,
|
|
||||||
Overlay,
|
|
||||||
Study,
|
|
||||||
chartTypeLabels,
|
|
||||||
intervalLabels,
|
|
||||||
overlayLabels,
|
|
||||||
studyLabels,
|
|
||||||
} from 'pennant';
|
|
||||||
import { VegaDataSource } from './data-source';
|
import { VegaDataSource } from './data-source';
|
||||||
import { useApolloClient } from '@apollo/client';
|
import { useApolloClient } from '@apollo/client';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import {
|
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||||
useThemeSwitcher,
|
|
||||||
getValidItem,
|
|
||||||
getValidSubset,
|
|
||||||
} from '@vegaprotocol/react-helpers';
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuCheckboxItem,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItemIndicator,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
Icon,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import type { IconName } from '@blueprintjs/icons';
|
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { create } from 'zustand';
|
import { useCandlesChartSettings } from './use-candles-chart-settings';
|
||||||
import { persist } from 'zustand/middleware';
|
|
||||||
import { immer } from 'zustand/middleware/immer';
|
|
||||||
|
|
||||||
interface StoredSettings {
|
|
||||||
interval?: Interval;
|
|
||||||
type?: ChartType;
|
|
||||||
overlays?: Overlay[];
|
|
||||||
studies?: Study[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCandlesChartSettings = create<
|
|
||||||
StoredSettings & {
|
|
||||||
merge: (settings: StoredSettings) => void;
|
|
||||||
setType: (type: ChartType) => void;
|
|
||||||
setInterval: (interval: Interval) => void;
|
|
||||||
setOverlays: (overlays: Overlay[]) => void;
|
|
||||||
setStudies: (studies: Study[]) => void;
|
|
||||||
}
|
|
||||||
>()(
|
|
||||||
persist(
|
|
||||||
immer((set) => ({
|
|
||||||
merge: (settings: StoredSettings) =>
|
|
||||||
set((state) => {
|
|
||||||
Object.assign(state, settings);
|
|
||||||
}),
|
|
||||||
setType: (type: ChartType) =>
|
|
||||||
set((state) => {
|
|
||||||
state.type = type;
|
|
||||||
}),
|
|
||||||
setInterval: (interval: Interval) =>
|
|
||||||
set((state) => {
|
|
||||||
state.interval = interval;
|
|
||||||
}),
|
|
||||||
setOverlays: (overlays: Overlay[]) =>
|
|
||||||
set((state) => {
|
|
||||||
state.overlays = overlays;
|
|
||||||
}),
|
|
||||||
setStudies: (studies: Study[]) =>
|
|
||||||
set((state) => {
|
|
||||||
state.studies = studies;
|
|
||||||
}),
|
|
||||||
})),
|
|
||||||
{
|
|
||||||
name: 'console-candles',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const chartTypeIcon = new Map<ChartType, IconName>([
|
|
||||||
[ChartType.AREA, IconNames.TIMELINE_AREA_CHART],
|
|
||||||
[ChartType.CANDLE, IconNames.WATERFALL_CHART],
|
|
||||||
[ChartType.LINE, IconNames.TIMELINE_LINE_CHART],
|
|
||||||
[ChartType.OHLC, IconNames.WATERFALL_CHART],
|
|
||||||
]);
|
|
||||||
|
|
||||||
export type CandlesChartContainerProps = {
|
export type CandlesChartContainerProps = {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
@@ -99,161 +19,32 @@ export const CandlesChartContainer = ({
|
|||||||
const { pubKey } = useVegaWallet();
|
const { pubKey } = useVegaWallet();
|
||||||
const { theme } = useThemeSwitcher();
|
const { theme } = useThemeSwitcher();
|
||||||
|
|
||||||
const settings = useCandlesChartSettings();
|
const { interval, chartType, overlays, studies, merge } =
|
||||||
|
useCandlesChartSettings();
|
||||||
const interval: Interval = getValidItem(
|
|
||||||
settings.interval,
|
|
||||||
Object.values(Interval),
|
|
||||||
Interval.I15M
|
|
||||||
);
|
|
||||||
|
|
||||||
const chartType: ChartType = getValidItem(
|
|
||||||
settings.type,
|
|
||||||
Object.values(ChartType),
|
|
||||||
ChartType.CANDLE
|
|
||||||
);
|
|
||||||
|
|
||||||
const overlays: Overlay[] = getValidSubset(
|
|
||||||
settings.overlays,
|
|
||||||
Object.values(Overlay),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const studies: Study[] = getValidSubset(
|
|
||||||
settings.studies,
|
|
||||||
Object.values(Study),
|
|
||||||
[Study.VOLUME]
|
|
||||||
);
|
|
||||||
|
|
||||||
const dataSource = useMemo(() => {
|
const dataSource = useMemo(() => {
|
||||||
return new VegaDataSource(client, marketId, pubKey);
|
return new VegaDataSource(client, marketId, pubKey);
|
||||||
}, [client, marketId, pubKey]);
|
}, [client, marketId, pubKey]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<CandlestickChart
|
||||||
<div className="px-3 lg:px-4 py-2 flex flex-row flex-wrap gap-2 bg-vega-clight-700 dark:bg-vega-cdark-700">
|
dataSource={dataSource}
|
||||||
<DropdownMenu
|
options={{
|
||||||
trigger={
|
chartType,
|
||||||
<DropdownMenuTrigger>
|
overlays,
|
||||||
{t(`Interval: ${intervalLabels[interval]}`)}
|
studies,
|
||||||
</DropdownMenuTrigger>
|
notEnoughDataText: (
|
||||||
}
|
<span className="text-xs text-center">{t('No data')}</span>
|
||||||
>
|
),
|
||||||
<DropdownMenuContent>
|
}}
|
||||||
<DropdownMenuRadioGroup
|
interval={interval}
|
||||||
value={interval}
|
theme={theme}
|
||||||
onValueChange={(value) => {
|
onOptionsChanged={(options) => {
|
||||||
settings.setInterval(value as Interval);
|
merge({
|
||||||
}}
|
overlays: options.overlays,
|
||||||
>
|
studies: options.studies,
|
||||||
{Object.values(Interval).map((timeInterval) => (
|
});
|
||||||
<DropdownMenuRadioItem
|
}}
|
||||||
key={timeInterval}
|
/>
|
||||||
inset
|
|
||||||
value={timeInterval}
|
|
||||||
>
|
|
||||||
{intervalLabels[timeInterval]}
|
|
||||||
<DropdownMenuItemIndicator />
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<DropdownMenu
|
|
||||||
trigger={
|
|
||||||
<DropdownMenuTrigger>
|
|
||||||
<Icon name={chartTypeIcon.get(chartType) as IconName} />
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DropdownMenuContent>
|
|
||||||
<DropdownMenuRadioGroup
|
|
||||||
value={chartType}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
settings.setType(value as ChartType);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Object.values(ChartType).map((type) => (
|
|
||||||
<DropdownMenuRadioItem key={type} inset value={type}>
|
|
||||||
{chartTypeLabels[type]}
|
|
||||||
<DropdownMenuItemIndicator />
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<DropdownMenu
|
|
||||||
trigger={<DropdownMenuTrigger>{t('Overlays')}</DropdownMenuTrigger>}
|
|
||||||
>
|
|
||||||
<DropdownMenuContent>
|
|
||||||
{Object.values(Overlay).map((overlay) => (
|
|
||||||
<DropdownMenuCheckboxItem
|
|
||||||
key={overlay}
|
|
||||||
checked={overlays.includes(overlay)}
|
|
||||||
onCheckedChange={() => {
|
|
||||||
const newOverlays = [...overlays];
|
|
||||||
const index = overlays.findIndex((item) => item === overlay);
|
|
||||||
|
|
||||||
index !== -1
|
|
||||||
? newOverlays.splice(index, 1)
|
|
||||||
: newOverlays.push(overlay);
|
|
||||||
|
|
||||||
settings.setOverlays(newOverlays);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{overlayLabels[overlay]}
|
|
||||||
<DropdownMenuItemIndicator />
|
|
||||||
</DropdownMenuCheckboxItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<DropdownMenu
|
|
||||||
trigger={<DropdownMenuTrigger>{t('Studies')}</DropdownMenuTrigger>}
|
|
||||||
>
|
|
||||||
<DropdownMenuContent>
|
|
||||||
{Object.values(Study).map((study) => (
|
|
||||||
<DropdownMenuCheckboxItem
|
|
||||||
key={study}
|
|
||||||
checked={studies.includes(study)}
|
|
||||||
onCheckedChange={() => {
|
|
||||||
const newStudies = [...studies];
|
|
||||||
const index = studies.findIndex((item) => item === study);
|
|
||||||
|
|
||||||
index !== -1
|
|
||||||
? newStudies.splice(index, 1)
|
|
||||||
: newStudies.push(study);
|
|
||||||
|
|
||||||
settings.setStudies(newStudies);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{studyLabels[study]}
|
|
||||||
<DropdownMenuItemIndicator />
|
|
||||||
</DropdownMenuCheckboxItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<CandlestickChart
|
|
||||||
dataSource={dataSource}
|
|
||||||
options={{
|
|
||||||
chartType: chartType,
|
|
||||||
overlays: overlays,
|
|
||||||
studies: studies,
|
|
||||||
notEnoughDataText: (
|
|
||||||
<span className="text-xs text-center">{t('No data')}</span>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
interval={interval}
|
|
||||||
theme={theme}
|
|
||||||
onOptionsChanged={(options) => {
|
|
||||||
settings.merge({
|
|
||||||
overlays: options.overlays,
|
|
||||||
studies: options.studies,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { CandlesMenu } from './candles-menu';
|
||||||
|
|
||||||
|
describe('CandlesMenu', () => {
|
||||||
|
it('should render with volume study showing by default', async () => {
|
||||||
|
render(<CandlesMenu />);
|
||||||
|
|
||||||
|
await userEvent.click(
|
||||||
|
screen.getByText('Studies', {
|
||||||
|
selector: '[type="button"]',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(await screen.findByRole('menu')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Volume')).toHaveAttribute('data-state', 'checked');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import 'pennant/dist/style.css';
|
||||||
|
import {
|
||||||
|
ChartType,
|
||||||
|
Interval,
|
||||||
|
Overlay,
|
||||||
|
Study,
|
||||||
|
chartTypeLabels,
|
||||||
|
intervalLabels,
|
||||||
|
overlayLabels,
|
||||||
|
studyLabels,
|
||||||
|
} from 'pennant';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItemIndicator,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
Icon,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { IconName } from '@blueprintjs/icons';
|
||||||
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { useCandlesChartSettings } from './use-candles-chart-settings';
|
||||||
|
|
||||||
|
const chartTypeIcon = new Map<ChartType, IconName>([
|
||||||
|
[ChartType.AREA, IconNames.TIMELINE_AREA_CHART],
|
||||||
|
[ChartType.CANDLE, IconNames.WATERFALL_CHART],
|
||||||
|
[ChartType.LINE, IconNames.TIMELINE_LINE_CHART],
|
||||||
|
[ChartType.OHLC, IconNames.WATERFALL_CHART],
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CandlesMenu = () => {
|
||||||
|
const {
|
||||||
|
interval,
|
||||||
|
chartType,
|
||||||
|
studies,
|
||||||
|
overlays,
|
||||||
|
setInterval,
|
||||||
|
setType,
|
||||||
|
setStudies,
|
||||||
|
setOverlays,
|
||||||
|
} = useCandlesChartSettings();
|
||||||
|
const triggerClasses = 'text-xs';
|
||||||
|
const contentAlign = 'end';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
|
<DropdownMenuTrigger className={triggerClasses}>
|
||||||
|
{t(`Interval: ${intervalLabels[interval]}`)}
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DropdownMenuContent align={contentAlign}>
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
value={interval}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setInterval(value as Interval);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.values(Interval).map((timeInterval) => (
|
||||||
|
<DropdownMenuRadioItem
|
||||||
|
key={timeInterval}
|
||||||
|
inset
|
||||||
|
value={timeInterval}
|
||||||
|
>
|
||||||
|
{intervalLabels[timeInterval]}
|
||||||
|
<DropdownMenuItemIndicator />
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
|
<DropdownMenuTrigger className={triggerClasses}>
|
||||||
|
<Icon name={chartTypeIcon.get(chartType) as IconName} />
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DropdownMenuContent align={contentAlign}>
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
value={chartType}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setType(value as ChartType);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.values(ChartType).map((type) => (
|
||||||
|
<DropdownMenuRadioItem key={type} inset value={type}>
|
||||||
|
{chartTypeLabels[type]}
|
||||||
|
<DropdownMenuItemIndicator />
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
|
<DropdownMenuTrigger className={triggerClasses}>
|
||||||
|
{t('Overlays')}
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DropdownMenuContent align={contentAlign}>
|
||||||
|
{Object.values(Overlay).map((overlay) => (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={overlay}
|
||||||
|
checked={overlays.includes(overlay)}
|
||||||
|
onCheckedChange={() => {
|
||||||
|
const newOverlays = [...overlays];
|
||||||
|
const index = overlays.findIndex((item) => item === overlay);
|
||||||
|
|
||||||
|
index !== -1
|
||||||
|
? newOverlays.splice(index, 1)
|
||||||
|
: newOverlays.push(overlay);
|
||||||
|
|
||||||
|
setOverlays(newOverlays);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{overlayLabels[overlay]}
|
||||||
|
<DropdownMenuItemIndicator />
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
|
<DropdownMenuTrigger className={triggerClasses}>
|
||||||
|
{t('Studies')}
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DropdownMenuContent align={contentAlign}>
|
||||||
|
{Object.values(Study).map((study) => (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={study}
|
||||||
|
checked={studies.includes(study)}
|
||||||
|
onCheckedChange={() => {
|
||||||
|
const newStudies = [...studies];
|
||||||
|
const index = studies.findIndex((item) => item === study);
|
||||||
|
|
||||||
|
index !== -1
|
||||||
|
? newStudies.splice(index, 1)
|
||||||
|
: newStudies.push(study);
|
||||||
|
|
||||||
|
setStudies(newStudies);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{studyLabels[study]}
|
||||||
|
<DropdownMenuItemIndicator />
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './__generated__/Candles';
|
export * from './__generated__/Candles';
|
||||||
export * from './__generated__/Chart';
|
export * from './__generated__/Chart';
|
||||||
export * from './candles-chart';
|
export * from './candles-chart';
|
||||||
|
export * from './candles-menu';
|
||||||
export * from './data-source';
|
export * from './data-source';
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { getValidItem, getValidSubset } from '@vegaprotocol/react-helpers';
|
||||||
|
import { ChartType, Interval, Study } from 'pennant';
|
||||||
|
import { Overlay } from 'pennant';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
import { immer } from 'zustand/middleware/immer';
|
||||||
|
|
||||||
|
interface StoredSettings {
|
||||||
|
interval: Interval;
|
||||||
|
type: ChartType;
|
||||||
|
overlays: Overlay[];
|
||||||
|
studies: Study[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CHART_SETTINGS = {
|
||||||
|
interval: Interval.I15M,
|
||||||
|
type: ChartType.CANDLE,
|
||||||
|
overlays: [],
|
||||||
|
studies: [Study.VOLUME],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCandlesChartSettingsStore = create<
|
||||||
|
StoredSettings & {
|
||||||
|
merge: (settings: Partial<StoredSettings>) => void;
|
||||||
|
setType: (type: ChartType) => void;
|
||||||
|
setInterval: (interval: Interval) => void;
|
||||||
|
setOverlays: (overlays: Overlay[]) => void;
|
||||||
|
setStudies: (studies: Study[]) => void;
|
||||||
|
}
|
||||||
|
>()(
|
||||||
|
persist(
|
||||||
|
immer((set) => ({
|
||||||
|
...DEFAULT_CHART_SETTINGS,
|
||||||
|
merge: (settings: Partial<StoredSettings>) =>
|
||||||
|
set((state) => {
|
||||||
|
Object.assign(state, settings);
|
||||||
|
}),
|
||||||
|
setType: (type) =>
|
||||||
|
set((state) => {
|
||||||
|
state.type = type;
|
||||||
|
}),
|
||||||
|
setInterval: (interval) =>
|
||||||
|
set((state) => {
|
||||||
|
state.interval = interval;
|
||||||
|
}),
|
||||||
|
setOverlays: (overlays) =>
|
||||||
|
set((state) => {
|
||||||
|
state.overlays = overlays;
|
||||||
|
}),
|
||||||
|
setStudies: (studies) =>
|
||||||
|
set((state) => {
|
||||||
|
state.studies = studies;
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
name: 'vega_candles_chart_store',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useCandlesChartSettings = () => {
|
||||||
|
const settings = useCandlesChartSettingsStore();
|
||||||
|
|
||||||
|
const interval: Interval = getValidItem(
|
||||||
|
settings.interval,
|
||||||
|
Object.values(Interval),
|
||||||
|
Interval.I15M
|
||||||
|
);
|
||||||
|
|
||||||
|
const chartType: ChartType = getValidItem(
|
||||||
|
settings.type,
|
||||||
|
Object.values(ChartType),
|
||||||
|
ChartType.CANDLE
|
||||||
|
);
|
||||||
|
|
||||||
|
const overlays: Overlay[] = getValidSubset(
|
||||||
|
settings.overlays,
|
||||||
|
Object.values(Overlay),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const studies: Study[] = getValidSubset(
|
||||||
|
settings.studies,
|
||||||
|
Object.values(Study),
|
||||||
|
[Study.VOLUME]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
interval,
|
||||||
|
chartType,
|
||||||
|
overlays,
|
||||||
|
studies,
|
||||||
|
setInterval: settings.setInterval,
|
||||||
|
setType: settings.setType,
|
||||||
|
setStudies: settings.setStudies,
|
||||||
|
setOverlays: settings.setOverlays,
|
||||||
|
merge: settings.merge,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -12,6 +12,7 @@ import { addUpdateCapsuleMultiSig } from './lib/commands/add-validators-to-multi
|
|||||||
import {
|
import {
|
||||||
addVegaWalletConnect,
|
addVegaWalletConnect,
|
||||||
addSetVegaWallet,
|
addSetVegaWallet,
|
||||||
|
addSetOnBoardingViewed,
|
||||||
} from './lib/commands/vega-wallet-connect';
|
} from './lib/commands/vega-wallet-connect';
|
||||||
import { addMockTransactionResponse } from './lib/commands/mock-transaction-response';
|
import { addMockTransactionResponse } from './lib/commands/mock-transaction-response';
|
||||||
import { addCreateMarket } from './lib/commands/create-market';
|
import { addCreateMarket } from './lib/commands/create-market';
|
||||||
@@ -38,6 +39,7 @@ addGetNetworkParameters();
|
|||||||
addUpdateCapsuleMultiSig();
|
addUpdateCapsuleMultiSig();
|
||||||
addVegaWalletConnect();
|
addVegaWalletConnect();
|
||||||
addSetVegaWallet();
|
addSetVegaWallet();
|
||||||
|
addSetOnBoardingViewed();
|
||||||
addMockTransactionResponse();
|
addMockTransactionResponse();
|
||||||
addCreateMarket();
|
addCreateMarket();
|
||||||
addConnectPublicKey();
|
addConnectPublicKey();
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ declare global {
|
|||||||
interface Chainable<Subject> {
|
interface Chainable<Subject> {
|
||||||
setVegaWallet(): void;
|
setVegaWallet(): void;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
interface Chainable<Subject> {
|
||||||
|
setOnBoardingViewed(): void;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +64,7 @@ export function addVegaWalletConnect() {
|
|||||||
export function addSetVegaWallet() {
|
export function addSetVegaWallet() {
|
||||||
Cypress.Commands.add('setVegaWallet', () => {
|
Cypress.Commands.add('setVegaWallet', () => {
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
win.localStorage.setItem('vega_risk_accepted', 'true');
|
win.localStorage.setItem('vega_onboarding_viewed', 'true');
|
||||||
win.localStorage.setItem(
|
win.localStorage.setItem(
|
||||||
'vega_wallet_config',
|
'vega_wallet_config',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -72,3 +76,11 @@ export function addSetVegaWallet() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addSetOnBoardingViewed() {
|
||||||
|
Cypress.Commands.add('setOnBoardingViewed', () => {
|
||||||
|
cy.window().then((win) => {
|
||||||
|
win.localStorage.setItem('vega_onboarding_viewed', 'true');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -488,19 +488,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
|||||||
</InputError>
|
</InputError>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 pb-2 justify-end">
|
<div className="flex gap-2 pb-2 justify-between">
|
||||||
<Checkbox
|
|
||||||
name="reduce-only"
|
|
||||||
checked={true}
|
|
||||||
disabled={true}
|
|
||||||
label={
|
|
||||||
<Tooltip description={<span>{t(REDUCE_ONLY_TOOLTIP)}</span>}>
|
|
||||||
<span className="text-xs">{t('Reduce only')}</span>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-2">
|
|
||||||
<Controller
|
<Controller
|
||||||
name="expire"
|
name="expire"
|
||||||
control={control}
|
control={control}
|
||||||
@@ -511,11 +499,21 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
|||||||
onCheckedChange={onCheckedChange}
|
onCheckedChange={onCheckedChange}
|
||||||
checked={value}
|
checked={value}
|
||||||
name="expire"
|
name="expire"
|
||||||
label={'Expire'}
|
label={<span className="text-xs">{t('Expire')}</span>}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Checkbox
|
||||||
|
name="reduce-only"
|
||||||
|
checked={true}
|
||||||
|
disabled={true}
|
||||||
|
label={
|
||||||
|
<Tooltip description={<span>{t(REDUCE_ONLY_TOOLTIP)}</span>}>
|
||||||
|
<span className="text-xs">{t('Reduce only')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{expire && (
|
{expire && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const SideSelector = (props: SideSelectorProps) => {
|
|||||||
return (
|
return (
|
||||||
<RadioGroup.Root
|
<RadioGroup.Root
|
||||||
name="order-side"
|
name="order-side"
|
||||||
className="mb-2 flex h-10 leading-10"
|
className="mb-4 flex h-10 leading-10"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{toggles.map(({ label, value }) => (
|
{toggles.map(({ label, value }) => (
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
Icon,
|
|
||||||
InputError,
|
InputError,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -10,6 +9,8 @@ import {
|
|||||||
TradingDropdownRadioGroup,
|
TradingDropdownRadioGroup,
|
||||||
TradingDropdownRadioItem,
|
TradingDropdownRadioItem,
|
||||||
TradingDropdownTrigger,
|
TradingDropdownTrigger,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
|
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
|
||||||
@@ -45,7 +46,7 @@ export const TypeToggle = ({
|
|||||||
return (
|
return (
|
||||||
<RadioGroup.Root
|
<RadioGroup.Root
|
||||||
name="order-type"
|
name="order-type"
|
||||||
className={classNames('mb-2 grid h-8 leading-8 font-alpha text-sm', {
|
className={classNames('mb-2 grid h-8 leading-8 font-alpha text-xs', {
|
||||||
'grid-cols-3': FLAGS.STOP_ORDERS,
|
'grid-cols-3': FLAGS.STOP_ORDERS,
|
||||||
'grid-cols-2': !FLAGS.STOP_ORDERS,
|
'grid-cols-2': !FLAGS.STOP_ORDERS,
|
||||||
})}
|
})}
|
||||||
@@ -81,11 +82,11 @@ export const TypeToggle = ({
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<button>
|
<button className="flex gap-1">
|
||||||
<span className="text-ellipsis whitespace-nowrap shrink overflow-hidden">
|
<span className="text-ellipsis whitespace-nowrap shrink overflow-hidden">
|
||||||
{t(selectedOption ? selectedOption.label : 'Stop')}
|
{t(selectedOption ? selectedOption.label : 'Stop')}
|
||||||
</span>
|
</span>
|
||||||
<Icon name="chevron-down" className="ml-1" />
|
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />
|
||||||
</button>
|
</button>
|
||||||
</TradingDropdownTrigger>
|
</TradingDropdownTrigger>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const NodeSwitcherDialog = ({
|
|||||||
setOpen: (x: boolean) => void;
|
setOpen: (x: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onChange={setOpen} size="medium">
|
<Dialog open={open} onChange={setOpen} size="large">
|
||||||
<NodeSwitcher closeDialog={() => setOpen(false)} />
|
<NodeSwitcher closeDialog={() => setOpen(false)} />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { forwardRef, useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
addDecimalsFormatNumberQuantum,
|
addDecimalsFormatNumberQuantum,
|
||||||
@@ -9,7 +9,6 @@ import { t } from '@vegaprotocol/i18n';
|
|||||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||||
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
import { TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
|
||||||
import type {
|
import type {
|
||||||
ColDef,
|
ColDef,
|
||||||
ITooltipParams,
|
ITooltipParams,
|
||||||
@@ -40,168 +39,164 @@ export interface LiquidityTableProps
|
|||||||
quantum?: string | number;
|
quantum?: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
|
export const LiquidityTable = ({
|
||||||
(
|
symbol = '',
|
||||||
{ symbol = '', assetDecimalPlaces, stakeToCcyVolume, quantum, ...props },
|
assetDecimalPlaces,
|
||||||
ref
|
stakeToCcyVolume,
|
||||||
) => {
|
quantum,
|
||||||
const colDefs = useMemo(() => {
|
...props
|
||||||
const assetDecimalsFormatter = ({ value }: ITooltipParams) => {
|
}: LiquidityTableProps) => {
|
||||||
if (!value) return '-';
|
const colDefs = useMemo(() => {
|
||||||
return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0)}`;
|
const assetDecimalsFormatter = ({ value }: ITooltipParams) => {
|
||||||
};
|
if (!value) return '-';
|
||||||
|
return `${addDecimalsFormatNumber(value, assetDecimalPlaces ?? 0)}`;
|
||||||
|
};
|
||||||
|
|
||||||
const assetDecimalsQuantumFormatter = ({
|
const assetDecimalsQuantumFormatter = ({ value }: ValueFormatterParams) => {
|
||||||
|
if (!value) return '-';
|
||||||
|
return `${addDecimalsFormatNumberQuantum(
|
||||||
value,
|
value,
|
||||||
}: ValueFormatterParams) => {
|
assetDecimalPlaces ?? 0,
|
||||||
if (!value) return '-';
|
quantum ?? 0
|
||||||
return `${addDecimalsFormatNumberQuantum(
|
)}`;
|
||||||
value,
|
};
|
||||||
assetDecimalPlaces ?? 0,
|
|
||||||
quantum ?? 0
|
|
||||||
)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const stakeToCcyVolumeFormatter = ({ value }: ITooltipParams) => {
|
const stakeToCcyVolumeFormatter = ({ value }: ITooltipParams) => {
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
const newValue = new BigNumber(value)
|
const newValue = new BigNumber(value)
|
||||||
.times(Number(stakeToCcyVolume) || 1)
|
.times(Number(stakeToCcyVolume) || 1)
|
||||||
.toString();
|
.toString();
|
||||||
return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0)}`;
|
return `${addDecimalsFormatNumber(newValue, assetDecimalPlaces ?? 0)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const stakeToCcyVolumeQuantumFormatter = ({
|
const stakeToCcyVolumeQuantumFormatter = ({
|
||||||
value,
|
value,
|
||||||
}: ValueFormatterParams) => {
|
}: ValueFormatterParams) => {
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
const newValue = new BigNumber(value)
|
const newValue = new BigNumber(value)
|
||||||
.times(Number(stakeToCcyVolume) || 1)
|
.times(Number(stakeToCcyVolume) || 1)
|
||||||
.toString();
|
.toString();
|
||||||
return `${addDecimalsFormatNumberQuantum(
|
return `${addDecimalsFormatNumberQuantum(
|
||||||
newValue,
|
newValue,
|
||||||
assetDecimalPlaces ?? 0,
|
assetDecimalPlaces ?? 0,
|
||||||
quantum ?? 0
|
quantum ?? 0
|
||||||
)}`;
|
)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defs: ColDef[] = [
|
const defs: ColDef[] = [
|
||||||
{
|
{
|
||||||
headerName: t('Party'),
|
headerName: t('Party'),
|
||||||
field: 'party.id',
|
field: 'party.id',
|
||||||
headerTooltip: t(
|
headerTooltip: t('The public key of the party making this commitment.'),
|
||||||
'The public key of the party making this commitment.'
|
},
|
||||||
),
|
{
|
||||||
|
headerName: t(`Commitment (${symbol})`),
|
||||||
|
field: 'commitmentAmount',
|
||||||
|
type: 'rightAligned',
|
||||||
|
headerTooltip: t(
|
||||||
|
'The amount committed to the market by this liquidity provider.'
|
||||||
|
),
|
||||||
|
valueFormatter: assetDecimalsQuantumFormatter,
|
||||||
|
tooltipValueGetter: assetDecimalsFormatter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t(`Share`),
|
||||||
|
field: 'equityLikeShare',
|
||||||
|
type: 'rightAligned',
|
||||||
|
headerTooltip: t(
|
||||||
|
'The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.'
|
||||||
|
),
|
||||||
|
valueFormatter: percentageFormatter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Proposed fee'),
|
||||||
|
headerTooltip: t(
|
||||||
|
'The fee percentage (per trade) proposed by each liquidity provider.'
|
||||||
|
),
|
||||||
|
field: 'fee',
|
||||||
|
type: 'rightAligned',
|
||||||
|
valueFormatter: percentageFormatter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Market valuation at entry'),
|
||||||
|
field: 'averageEntryValuation',
|
||||||
|
type: 'rightAligned',
|
||||||
|
headerTooltip: t(
|
||||||
|
'The valuation of the market at the time the liquidity commitment was made. Commitments made at a lower valuation earlier in the lifetime of the market would be expected to have a higher equity-like share if the market has grown. If a commitment is amended, value will reflect the average of the market valuations across the lifetime of the commitment.'
|
||||||
|
),
|
||||||
|
minWidth: 160,
|
||||||
|
valueFormatter: assetDecimalsQuantumFormatter,
|
||||||
|
tooltipValueGetter: assetDecimalsFormatter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Obligation'),
|
||||||
|
field: 'commitmentAmount',
|
||||||
|
type: 'rightAligned',
|
||||||
|
headerTooltip: t(
|
||||||
|
`The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_volume network parameter to convert into units of liquidity volume. The obligation can be met by a combination of LP orders and limit orders on the order book.`
|
||||||
|
),
|
||||||
|
valueFormatter: stakeToCcyVolumeQuantumFormatter,
|
||||||
|
tooltipValueGetter: stakeToCcyVolumeFormatter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Supplied'),
|
||||||
|
field: 'balance',
|
||||||
|
type: 'rightAligned',
|
||||||
|
headerTooltip: t(
|
||||||
|
`The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.`
|
||||||
|
),
|
||||||
|
valueFormatter: stakeToCcyVolumeQuantumFormatter,
|
||||||
|
tooltipValueGetter: stakeToCcyVolumeFormatter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Status'),
|
||||||
|
headerTooltip: t('The current status of this liquidity provision.'),
|
||||||
|
field: 'status',
|
||||||
|
valueFormatter: ({ value }) => {
|
||||||
|
if (!value) return value;
|
||||||
|
return LiquidityProvisionStatusMapping[
|
||||||
|
value as LiquidityProvisionStatus
|
||||||
|
];
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
headerName: t(`Commitment (${symbol})`),
|
{
|
||||||
field: 'commitmentAmount',
|
headerName: t('Created'),
|
||||||
type: 'rightAligned',
|
headerTooltip: t(
|
||||||
headerTooltip: t(
|
'The date and time this liquidity provision was created.'
|
||||||
'The amount committed to the market by this liquidity provider.'
|
),
|
||||||
),
|
field: 'createdAt',
|
||||||
valueFormatter: assetDecimalsQuantumFormatter,
|
type: 'rightAligned',
|
||||||
tooltipValueGetter: assetDecimalsFormatter,
|
valueFormatter: dateValueFormatter,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: t(`Share`),
|
headerName: t('Updated'),
|
||||||
field: 'equityLikeShare',
|
headerTooltip: t(
|
||||||
type: 'rightAligned',
|
'The date and time this liquidity provision was last updated.'
|
||||||
headerTooltip: t(
|
),
|
||||||
'The equity-like share of liquidity of the market used to determine allocation of LP fees. Calculated based on share of total liquidity, with a premium added for length of commitment.'
|
field: 'updatedAt',
|
||||||
),
|
type: 'rightAligned',
|
||||||
valueFormatter: percentageFormatter,
|
valueFormatter: dateValueFormatter,
|
||||||
},
|
},
|
||||||
{
|
];
|
||||||
headerName: t('Proposed fee'),
|
return defs;
|
||||||
headerTooltip: t(
|
}, [assetDecimalPlaces, quantum, stakeToCcyVolume, symbol]);
|
||||||
'The fee percentage (per trade) proposed by each liquidity provider.'
|
|
||||||
),
|
|
||||||
field: 'fee',
|
|
||||||
type: 'rightAligned',
|
|
||||||
valueFormatter: percentageFormatter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Market valuation at entry'),
|
|
||||||
field: 'averageEntryValuation',
|
|
||||||
type: 'rightAligned',
|
|
||||||
headerTooltip: t(
|
|
||||||
'The valuation of the market at the time the liquidity commitment was made. Commitments made at a lower valuation earlier in the lifetime of the market would be expected to have a higher equity-like share if the market has grown. If a commitment is amended, value will reflect the average of the market valuations across the lifetime of the commitment.'
|
|
||||||
),
|
|
||||||
minWidth: 160,
|
|
||||||
valueFormatter: assetDecimalsQuantumFormatter,
|
|
||||||
tooltipValueGetter: assetDecimalsFormatter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Obligation'),
|
|
||||||
field: 'commitmentAmount',
|
|
||||||
type: 'rightAligned',
|
|
||||||
headerTooltip: t(
|
|
||||||
`The liquidity provider's obligation to the market, calculated as the liquidity commitment amount multiplied by the value of the stake_to_ccy_volume network parameter to convert into units of liquidity volume. The obligation can be met by a combination of LP orders and limit orders on the order book.`
|
|
||||||
),
|
|
||||||
valueFormatter: stakeToCcyVolumeQuantumFormatter,
|
|
||||||
tooltipValueGetter: stakeToCcyVolumeFormatter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Supplied'),
|
|
||||||
field: 'balance',
|
|
||||||
type: 'rightAligned',
|
|
||||||
headerTooltip: t(
|
|
||||||
`The amount of liquidity volume supplied by the LP order in order to meet the obligation. If the obligation is already met in full by other limit orders from the same Vega key the LP order is not required and this value will be zero. Also note if the target stake for the market is less than the obligation the full value of the obligation may not be required.`
|
|
||||||
),
|
|
||||||
valueFormatter: stakeToCcyVolumeQuantumFormatter,
|
|
||||||
tooltipValueGetter: stakeToCcyVolumeFormatter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Status'),
|
|
||||||
headerTooltip: t('The current status of this liquidity provision.'),
|
|
||||||
field: 'status',
|
|
||||||
valueFormatter: ({ value }) => {
|
|
||||||
if (!value) return value;
|
|
||||||
return LiquidityProvisionStatusMapping[
|
|
||||||
value as LiquidityProvisionStatus
|
|
||||||
];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Created'),
|
|
||||||
headerTooltip: t(
|
|
||||||
'The date and time this liquidity provision was created.'
|
|
||||||
),
|
|
||||||
field: 'createdAt',
|
|
||||||
type: 'rightAligned',
|
|
||||||
valueFormatter: dateValueFormatter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerName: t('Updated'),
|
|
||||||
headerTooltip: t(
|
|
||||||
'The date and time this liquidity provision was last updated.'
|
|
||||||
),
|
|
||||||
field: 'updatedAt',
|
|
||||||
type: 'rightAligned',
|
|
||||||
valueFormatter: dateValueFormatter,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return defs;
|
|
||||||
}, [assetDecimalPlaces, quantum, stakeToCcyVolume, symbol]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AgGrid
|
<AgGrid
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
overlayNoRowsTemplate={t('No liquidity provisions')}
|
overlayNoRowsTemplate={t('No liquidity provisions')}
|
||||||
getRowId={({ data }: { data: LiquidityProvisionData }) => data.id || ''}
|
getRowId={({ data }: { data: LiquidityProvisionData }) => data.id || ''}
|
||||||
ref={ref}
|
tooltipShowDelay={500}
|
||||||
tooltipShowDelay={500}
|
defaultColDef={{
|
||||||
defaultColDef={{
|
resizable: true,
|
||||||
resizable: true,
|
minWidth: 100,
|
||||||
minWidth: 100,
|
tooltipComponent: TooltipCellComponent,
|
||||||
tooltipComponent: TooltipCellComponent,
|
sortable: true,
|
||||||
sortable: true,
|
}}
|
||||||
}}
|
{...props}
|
||||||
{...props}
|
columnDefs={colDefs}
|
||||||
columnDefs={colDefs}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
};
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default LiquidityTable;
|
export default LiquidityTable;
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './order-actions-dropdown';
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import {
|
|
||||||
ActionsDropdown,
|
|
||||||
TradingDropdownCopyItem,
|
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
|
|
||||||
export const OrderActionsDropdown = ({ id }: { id: string }) => {
|
|
||||||
return (
|
|
||||||
<ActionsDropdown data-testid="order-actions-content">
|
|
||||||
<TradingDropdownCopyItem value={id} text={t('Copy order ID')} />
|
|
||||||
</ActionsDropdown>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
||||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||||
import type { Order } from '../order-data-provider';
|
import type { Order } from '../order-data-provider';
|
||||||
|
import { OrderViewDialog } from '../order-list/order-view-dialog';
|
||||||
|
|
||||||
export enum Filter {
|
export enum Filter {
|
||||||
'Open' = 'Open',
|
'Open' = 'Open',
|
||||||
@@ -42,6 +43,7 @@ export const OrderListManager = ({
|
|||||||
}: OrderListManagerProps) => {
|
}: OrderListManagerProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||||
|
const [viewOrder, setViewOrder] = useState<Order | null>(null);
|
||||||
const create = useVegaTransactionStore((state) => state.create);
|
const create = useVegaTransactionStore((state) => state.create);
|
||||||
const hasAmendableOrder = useHasAmendableOrder(marketId);
|
const hasAmendableOrder = useHasAmendableOrder(marketId);
|
||||||
const variables =
|
const variables =
|
||||||
@@ -89,6 +91,7 @@ export const OrderListManager = ({
|
|||||||
filter={filter}
|
filter={filter}
|
||||||
onCancel={cancel}
|
onCancel={cancel}
|
||||||
onEdit={setEditOrder}
|
onEdit={setEditOrder}
|
||||||
|
onView={setViewOrder}
|
||||||
onMarketClick={onMarketClick}
|
onMarketClick={onMarketClick}
|
||||||
onOrderTypeClick={onOrderTypeClick}
|
onOrderTypeClick={onOrderTypeClick}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
@@ -134,6 +137,14 @@ export const OrderListManager = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{viewOrder && (
|
||||||
|
<OrderViewDialog
|
||||||
|
isOpen={Boolean(viewOrder)}
|
||||||
|
order={viewOrder}
|
||||||
|
onChange={() => setViewOrder(null)}
|
||||||
|
onMarketClick={onMarketClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { PartialDeep } from 'type-fest';
|
|||||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import type { OrderListTableProps } from '../';
|
import type { OrderFieldsFragment, OrderListTableProps } from '../';
|
||||||
import { OrderListTable } from '../';
|
import { OrderListTable } from '../';
|
||||||
import {
|
import {
|
||||||
generateOrder,
|
generateOrder,
|
||||||
@@ -27,6 +27,7 @@ const defaultProps: OrderListTableProps = {
|
|||||||
rowData: [],
|
rowData: [],
|
||||||
onEdit: jest.fn(),
|
onEdit: jest.fn(),
|
||||||
onCancel: jest.fn(),
|
onCancel: jest.fn(),
|
||||||
|
onView: jest.fn(),
|
||||||
isReadOnly: false,
|
isReadOnly: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,6 +111,39 @@ describe('OrderListTable', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should apply correct formatting applied for an iceberg order', async () => {
|
||||||
|
const icebergOrder = {
|
||||||
|
...limitOrder,
|
||||||
|
icebergOrder: {
|
||||||
|
__typename: 'IcebergOrder',
|
||||||
|
minimumVisibleSize: '100',
|
||||||
|
peakSize: '50',
|
||||||
|
reservedRemaining: '50',
|
||||||
|
} as OrderFieldsFragment['icebergOrder'],
|
||||||
|
};
|
||||||
|
await act(async () => {
|
||||||
|
render(generateJsx({ rowData: [icebergOrder] }));
|
||||||
|
});
|
||||||
|
const cells = screen.getAllByRole('gridcell');
|
||||||
|
const expectedValues: string[] = [
|
||||||
|
icebergOrder.market?.tradableInstrument.instrument.code || '',
|
||||||
|
'0.05',
|
||||||
|
'0.10',
|
||||||
|
Schema.OrderTypeMapping[
|
||||||
|
icebergOrder.type || Schema.OrderType.TYPE_LIMIT
|
||||||
|
] + ' (Iceberg)',
|
||||||
|
Schema.OrderStatusMapping[icebergOrder.status],
|
||||||
|
'-',
|
||||||
|
`${
|
||||||
|
Schema.OrderTimeInForceCode[icebergOrder.timeInForce]
|
||||||
|
}: ${getDateTimeFormat().format(new Date(icebergOrder.expiresAt ?? ''))}`,
|
||||||
|
getDateTimeFormat().format(new Date(icebergOrder.createdAt)),
|
||||||
|
];
|
||||||
|
expectedValues.forEach((expectedValue, i) =>
|
||||||
|
expect(cells[i]).toHaveTextContent(expectedValue)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should apply correct formatting for a rejected order', async () => {
|
it('should apply correct formatting for a rejected order', async () => {
|
||||||
const rejectedOrder = {
|
const rejectedOrder = {
|
||||||
...marketOrder,
|
...marketOrder,
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ const Template: Story = (args) => {
|
|||||||
onEdit={() => {
|
onEdit={() => {
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
|
onView={() => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
isReadOnly={false}
|
isReadOnly={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,6 +52,9 @@ const Template2: Story = (args) => {
|
|||||||
rowData={args.data}
|
rowData={args.data}
|
||||||
onCancel={cancel}
|
onCancel={cancel}
|
||||||
onEdit={setEditOrder}
|
onEdit={setEditOrder}
|
||||||
|
onView={() => {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
isReadOnly={false}
|
isReadOnly={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,14 @@ import {
|
|||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
ActionsDropdown,
|
||||||
|
ButtonLink,
|
||||||
|
TradingDropdownCopyItem,
|
||||||
|
DropdownMenuItem,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import type { ForwardedRef } from 'react';
|
import type { ForwardedRef } from 'react';
|
||||||
import { memo, forwardRef, useMemo } from 'react';
|
import { memo, forwardRef, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
@@ -27,7 +34,6 @@ import type {
|
|||||||
} from '@vegaprotocol/datagrid';
|
} from '@vegaprotocol/datagrid';
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
import type { Order } from '../order-data-provider';
|
import type { Order } from '../order-data-provider';
|
||||||
import { OrderActionsDropdown } from '../order-actions-dropdown';
|
|
||||||
import { Filter } from '../order-list-manager';
|
import { Filter } from '../order-list-manager';
|
||||||
import type { ColDef } from 'ag-grid-community';
|
import type { ColDef } from 'ag-grid-community';
|
||||||
|
|
||||||
@@ -35,6 +41,7 @@ export type OrderListTableProps = TypedDataAgGrid<Order> & {
|
|||||||
marketId?: string;
|
marketId?: string;
|
||||||
onCancel: (order: Order) => void;
|
onCancel: (order: Order) => void;
|
||||||
onEdit: (order: Order) => void;
|
onEdit: (order: Order) => void;
|
||||||
|
onView: (order: Order) => void;
|
||||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||||
filter?: Filter;
|
filter?: Filter;
|
||||||
@@ -46,7 +53,15 @@ export const OrderListTable = memo<
|
|||||||
>(
|
>(
|
||||||
forwardRef<AgGridReact, OrderListTableProps>(
|
forwardRef<AgGridReact, OrderListTableProps>(
|
||||||
(
|
(
|
||||||
{ onCancel, onEdit, onMarketClick, onOrderTypeClick, filter, ...props },
|
{
|
||||||
|
onCancel,
|
||||||
|
onEdit,
|
||||||
|
onView,
|
||||||
|
onMarketClick,
|
||||||
|
onOrderTypeClick,
|
||||||
|
filter,
|
||||||
|
...props
|
||||||
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const showAllActions = props.isReadOnly
|
const showAllActions = props.isReadOnly
|
||||||
@@ -69,6 +84,18 @@ export const OrderListTable = memo<
|
|||||||
cellClass: 'font-mono text-right',
|
cellClass: 'font-mono text-right',
|
||||||
type: 'rightAligned',
|
type: 'rightAligned',
|
||||||
valueGetter: ({ data }: VegaValueGetterParams<Order>) => {
|
valueGetter: ({ data }: VegaValueGetterParams<Order>) => {
|
||||||
|
if (data?.icebergOrder) {
|
||||||
|
return data?.size && data.market
|
||||||
|
? toBigNum(
|
||||||
|
(
|
||||||
|
BigInt(data.size) -
|
||||||
|
BigInt(data.remaining) -
|
||||||
|
BigInt(data.icebergOrder.reservedRemaining)
|
||||||
|
).toString(),
|
||||||
|
data.market.positionDecimalPlaces ?? 0
|
||||||
|
).toNumber()
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
return data?.size && data.market
|
return data?.size && data.market
|
||||||
? toBigNum(
|
? toBigNum(
|
||||||
(BigInt(data.size) - BigInt(data.remaining)).toString(),
|
(BigInt(data.size) - BigInt(data.remaining)).toString(),
|
||||||
@@ -283,7 +310,20 @@ export const OrderListTable = memo<
|
|||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<OrderActionsDropdown id={data?.id} />
|
<ActionsDropdown data-testid="market-actions-content">
|
||||||
|
<TradingDropdownCopyItem
|
||||||
|
value={data.id}
|
||||||
|
text={t('Copy order ID')}
|
||||||
|
/>
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={'view-order'}
|
||||||
|
data-testid="view-order"
|
||||||
|
onClick={() => onView(data)}
|
||||||
|
>
|
||||||
|
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||||
|
{t('View order details')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</ActionsDropdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -293,6 +333,7 @@ export const OrderListTable = memo<
|
|||||||
filter,
|
filter,
|
||||||
onCancel,
|
onCancel,
|
||||||
onEdit,
|
onEdit,
|
||||||
|
onView,
|
||||||
onMarketClick,
|
onMarketClick,
|
||||||
onOrderTypeClick,
|
onOrderTypeClick,
|
||||||
props.isReadOnly,
|
props.isReadOnly,
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { OrderViewDialog } from './order-view-dialog';
|
||||||
|
import type { Order } from '../order-data-provider';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
MarketState,
|
||||||
|
MarketTradingMode,
|
||||||
|
OrderStatus,
|
||||||
|
OrderTimeInForce,
|
||||||
|
OrderType,
|
||||||
|
Side,
|
||||||
|
} from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
describe('OrderViewDialog', () => {
|
||||||
|
it('should render the order view dialog if the order is provided', async () => {
|
||||||
|
const order: Order = {
|
||||||
|
id: '7f2a78f370062e41683d26a0fcc4521b2bd4b747530b78bc6bc86195db0e5fb3',
|
||||||
|
market: {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: 'b66cd4be223dfd900a4750bb5175e17d8f678996877d262be4c749a99e22a970',
|
||||||
|
decimalPlaces: 5,
|
||||||
|
positionDecimalPlaces: 3,
|
||||||
|
state: MarketState.STATE_ACTIVE,
|
||||||
|
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||||
|
fees: {
|
||||||
|
__typename: 'Fees',
|
||||||
|
factors: {
|
||||||
|
__typename: 'FeeFactors',
|
||||||
|
makerFee: '0.0002',
|
||||||
|
infrastructureFee: '0.0005',
|
||||||
|
liquidityFee: '0.001',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tradableInstrument: {
|
||||||
|
__typename: 'TradableInstrument',
|
||||||
|
instrument: {
|
||||||
|
__typename: 'Instrument',
|
||||||
|
id: '',
|
||||||
|
name: 'Tesla Quarterly (Sep 2023)',
|
||||||
|
code: 'TSLA.QM21',
|
||||||
|
metadata: {
|
||||||
|
__typename: 'InstrumentMetadata',
|
||||||
|
tags: [
|
||||||
|
'formerly:5A86B190C384997F',
|
||||||
|
'quote:EURO',
|
||||||
|
'ticker:TSLA',
|
||||||
|
'class:equities/single-stock-futures',
|
||||||
|
'sector:tech',
|
||||||
|
'listing_venue:NASDAQ',
|
||||||
|
'country:US',
|
||||||
|
'auto:tsla',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
__typename: 'Future',
|
||||||
|
settlementAsset: {
|
||||||
|
__typename: 'Asset',
|
||||||
|
id: '177e8f6c25a955bd18475084b99b2b1d37f28f3dec393fab7755a7e69c3d8c3b',
|
||||||
|
symbol: 'tEURO',
|
||||||
|
name: 'tEURO Fairground',
|
||||||
|
decimals: 5,
|
||||||
|
quantum: '1',
|
||||||
|
},
|
||||||
|
quoteName: 'EURO',
|
||||||
|
dataSourceSpecForTradingTermination: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: '3f5941ba047dabb3bec23f112db0a8cc8aecbbbc6b34d1366d9383ed1b0f39df',
|
||||||
|
data: {
|
||||||
|
__typename: 'DataSourceDefinition',
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceDefinitionExternal',
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceSpecConfiguration',
|
||||||
|
signers: [
|
||||||
|
{
|
||||||
|
__typename: 'Signer',
|
||||||
|
signer: {
|
||||||
|
__typename: 'PubKey',
|
||||||
|
key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
filters: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSourceSpecForSettlementData: {
|
||||||
|
__typename: 'DataSourceSpec',
|
||||||
|
id: '05ca5485678aa7c705aa6a683fd062714cbcfdac2d1a27641cd27b66168d9c1d',
|
||||||
|
data: {
|
||||||
|
__typename: 'DataSourceDefinition',
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceDefinitionExternal',
|
||||||
|
sourceType: {
|
||||||
|
__typename: 'DataSourceSpecConfiguration',
|
||||||
|
signers: [
|
||||||
|
{
|
||||||
|
__typename: 'Signer',
|
||||||
|
signer: {
|
||||||
|
__typename: 'PubKey',
|
||||||
|
key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
filters: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSourceSpecBinding: {
|
||||||
|
__typename: 'DataSourceSpecToFutureBinding',
|
||||||
|
settlementDataProperty: 'prices.TSLA.value',
|
||||||
|
tradingTerminationProperty: 'termination.TSLA.value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
marketTimestamps: {
|
||||||
|
__typename: 'MarketTimestamps',
|
||||||
|
open: '2023-07-19T12:05:25.822854221Z',
|
||||||
|
close: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: OrderType.TYPE_LIMIT,
|
||||||
|
side: Side.SIDE_BUY,
|
||||||
|
size: '10000',
|
||||||
|
status: OrderStatus.STATUS_ACTIVE,
|
||||||
|
rejectionReason: null,
|
||||||
|
price: '15000000',
|
||||||
|
timeInForce: OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||||
|
remaining: '5000',
|
||||||
|
expiresAt: null,
|
||||||
|
createdAt: '2023-08-01T14:47:34.977742Z',
|
||||||
|
updatedAt: null,
|
||||||
|
postOnly: false,
|
||||||
|
reduceOnly: false,
|
||||||
|
liquidityProvision: null,
|
||||||
|
peggedOrder: null,
|
||||||
|
icebergOrder: {
|
||||||
|
__typename: 'IcebergOrder',
|
||||||
|
peakSize: '5000',
|
||||||
|
minimumVisibleSize: '2000',
|
||||||
|
reservedRemaining: '5000',
|
||||||
|
},
|
||||||
|
__typename: 'Order',
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<OrderViewDialog order={order} onChange={jest.fn()} isOpen={true} />
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('order-market-label')).toHaveTextContent(
|
||||||
|
'Market'
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('order-market-value')).toHaveTextContent(
|
||||||
|
'Tesla Quarterly (Sep 2023)'
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('order-type-label')).toHaveTextContent('Type');
|
||||||
|
expect(screen.getByTestId('order-type-value')).toHaveTextContent('Limit');
|
||||||
|
expect(screen.getByTestId('order-price-label')).toHaveTextContent('Price');
|
||||||
|
expect(screen.getByTestId('order-price-value')).toHaveTextContent('150.00');
|
||||||
|
expect(screen.getByTestId('order-size-label')).toHaveTextContent('Size');
|
||||||
|
expect(screen.getByTestId('order-size-value')).toHaveTextContent('+10.00');
|
||||||
|
expect(screen.getByTestId('order-remaining-label')).toHaveTextContent(
|
||||||
|
'Remaining'
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('order-remaining-value')).toHaveTextContent(
|
||||||
|
'+5.00'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByTestId('order-iceberg-order-reserved-remaining-value')
|
||||||
|
).toHaveTextContent('5.00');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
import {
|
||||||
|
addDecimalsFormatNumber,
|
||||||
|
getDateTimeFormat,
|
||||||
|
} from '@vegaprotocol/utils';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { Size } from '@vegaprotocol/datagrid';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
import {
|
||||||
|
ButtonLink,
|
||||||
|
Dialog,
|
||||||
|
KeyValueTable,
|
||||||
|
KeyValueTableRow,
|
||||||
|
Tooltip,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
|
truncateMiddle,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { Order } from '../order-data-provider';
|
||||||
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||||
|
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
interface OrderViewDialogProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
order: Order;
|
||||||
|
onChange: (open: boolean) => void;
|
||||||
|
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrderViewDialog = ({
|
||||||
|
isOpen,
|
||||||
|
order,
|
||||||
|
onChange,
|
||||||
|
onMarketClick,
|
||||||
|
}: OrderViewDialogProps) => {
|
||||||
|
const [, setCopied] = useCopyTimeout();
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} title={t('Order details')} onChange={onChange}>
|
||||||
|
<KeyValueTable>
|
||||||
|
<KeyValueTableRow key={'order-market'}>
|
||||||
|
<div data-testid={'order-market-label'}>{t('Market')}</div>
|
||||||
|
<div data-testid={`order-market-value`}>
|
||||||
|
{onMarketClick ? (
|
||||||
|
<ButtonLink
|
||||||
|
onClick={() => order.market && onMarketClick(order.market?.id)}
|
||||||
|
>
|
||||||
|
{order.market?.tradableInstrument.instrument.name}
|
||||||
|
</ButtonLink>
|
||||||
|
) : (
|
||||||
|
order.market?.tradableInstrument.instrument.name
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
<KeyValueTableRow key={'order-type'}>
|
||||||
|
<div data-testid={'order-type-label'}>{t('Type')}</div>
|
||||||
|
<div data-testid={`order-type-value`}>
|
||||||
|
{Schema.OrderTypeMapping[order.type as Schema.OrderType]}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
{order.market && (
|
||||||
|
<KeyValueTableRow key={'order-price'}>
|
||||||
|
<div data-testid={'order-price-label'}>{t('Price')}</div>
|
||||||
|
<div data-testid={`order-price-value`}>
|
||||||
|
{addDecimalsFormatNumber(
|
||||||
|
order.price,
|
||||||
|
order.market.decimalPlaces as number
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{order.market && (
|
||||||
|
<KeyValueTableRow key={'order-size'}>
|
||||||
|
<div data-testid={'order-size-label'}>{t('Size')}</div>
|
||||||
|
<div data-testid={`order-size-value`}>
|
||||||
|
<Size
|
||||||
|
value={order.size}
|
||||||
|
side={order.side}
|
||||||
|
positionDecimalPlaces={
|
||||||
|
order.market.positionDecimalPlaces as number
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{order.market && (
|
||||||
|
<KeyValueTableRow key={'order-remaining'} className="mb-4">
|
||||||
|
<div data-testid={'order-remaining-label'}>{t('Remaining')}</div>
|
||||||
|
<div data-testid={`order-remaining-value`}>
|
||||||
|
<Size
|
||||||
|
value={order.remaining}
|
||||||
|
side={order.side}
|
||||||
|
positionDecimalPlaces={
|
||||||
|
order.market.positionDecimalPlaces as number
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
<KeyValueTableRow key={'order-status'}>
|
||||||
|
<div data-testid={'order-status-label'}>{t('Status')}</div>
|
||||||
|
<div data-testid={`order-status-value`}>
|
||||||
|
{Schema.OrderStatusMapping[order.status as Schema.OrderStatus]}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
{order.rejectionReason && (
|
||||||
|
<KeyValueTableRow key={'order-rejection-reason'}>
|
||||||
|
<div data-testid={'order-rejection-reason-label'}>
|
||||||
|
{t('rejection reason')}
|
||||||
|
</div>
|
||||||
|
<div data-testid={`order-rejection-reason-value`}>
|
||||||
|
{
|
||||||
|
Schema.OrderRejectionReasonMapping[
|
||||||
|
order.rejectionReason as Schema.OrderRejectionReason
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
<KeyValueTableRow key={'order-id'}>
|
||||||
|
<div data-testid={'order-id-label'}>{t('Order ID')}</div>
|
||||||
|
<div data-testid={`order-id-value`}>
|
||||||
|
{truncateMiddle(order.id, 10)}
|
||||||
|
<CopyToClipboard text={order.id} onCopy={() => setCopied(true)}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-testid="copy-order-id"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">{t('Copy')}</span>
|
||||||
|
<VegaIcon name={VegaIconNames.COPY} />
|
||||||
|
</button>
|
||||||
|
</CopyToClipboard>
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
<KeyValueTableRow key={'order-created'}>
|
||||||
|
<div data-testid={'order-created-label'}>{t('Created')}</div>
|
||||||
|
<div data-testid={`order-created-value`}>
|
||||||
|
{getDateTimeFormat().format(new Date(order.createdAt))}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
{order.updatedAt && (
|
||||||
|
<KeyValueTableRow key={'order-updated'}>
|
||||||
|
<div data-testid={'order-updated-label'}>{t('Updated')}</div>
|
||||||
|
<div data-testid={`order-updated-value`}>
|
||||||
|
{getDateTimeFormat().format(new Date(order.updatedAt))}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{order.expiresAt && (
|
||||||
|
<KeyValueTableRow key={'order-expires'}>
|
||||||
|
<div data-testid={'order-expires-label'}>{t('Expires')}</div>
|
||||||
|
<div data-testid={`order-expires-value`}>
|
||||||
|
{getDateTimeFormat().format(new Date(order.expiresAt))}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<KeyValueTableRow key={'order-time-in-force'} className="mt-4">
|
||||||
|
<div data-testid={'order-time-in-force-label'}>
|
||||||
|
{t('Time in force')}
|
||||||
|
</div>
|
||||||
|
<div data-testid={`order-time-in-force-value`}>
|
||||||
|
{
|
||||||
|
Schema.OrderTimeInForceMapping[
|
||||||
|
order.timeInForce as Schema.OrderTimeInForce
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
|
||||||
|
<KeyValueTableRow key={'order-post-only'}>
|
||||||
|
<div data-testid={'order-post-only-label'}>{t('Post only')}</div>
|
||||||
|
<div data-testid={`order-post-only-value`}>
|
||||||
|
{order.postOnly ? t('Yes') : t('-')}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
|
||||||
|
<KeyValueTableRow key={'order-reduce-only'}>
|
||||||
|
<div data-testid={'order-reduce-only-label'}>{t('Reduce only')}</div>
|
||||||
|
<div data-testid={`order-reduce-only-value`}>
|
||||||
|
{order.reduceOnly ? t('Yes') : t('-')}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
|
||||||
|
<KeyValueTableRow key={'order-pegged'}>
|
||||||
|
<div data-testid={'order-pegged-label'}>{t('Pegged')}</div>
|
||||||
|
<div data-testid={`order-pegged-value`}>
|
||||||
|
{order.peggedOrder ? t('Yes') : t('-')}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
|
||||||
|
<KeyValueTableRow key={'order-liquidity-provision'}>
|
||||||
|
<div data-testid={'order-liquidity-provision-label'}>
|
||||||
|
{t('Liquidity provision')}
|
||||||
|
</div>
|
||||||
|
<div data-testid={`order-liquidity-provision-value`}>
|
||||||
|
{order.liquidityProvision ? t('Yes') : t('-')}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
</KeyValueTable>
|
||||||
|
|
||||||
|
<KeyValueTableRow key={'order-iceberg-order'}>
|
||||||
|
<div data-testid={'order-iceberg-order-label'}>
|
||||||
|
{t('Iceberg order')}
|
||||||
|
</div>
|
||||||
|
<div data-testid={`order-iceberg-order-value`}>
|
||||||
|
{order.icebergOrder ? t('Yes') : t('-')}
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
{order.icebergOrder && (
|
||||||
|
<KeyValueTableRow
|
||||||
|
key={'order-iceberg-order-peak-size'}
|
||||||
|
className="ml-4"
|
||||||
|
>
|
||||||
|
<div data-testid={'order-iceberg-order-peak-size-label'}>
|
||||||
|
<Tooltip
|
||||||
|
description={t(
|
||||||
|
'The maximum volume that can be traded at once. Must be less than the total size of the order.'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{t('Peak size')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div data-testid={`order-iceberg-order-peak-size-value`}>
|
||||||
|
<Size
|
||||||
|
value={order.icebergOrder.peakSize}
|
||||||
|
side={order.side}
|
||||||
|
positionDecimalPlaces={
|
||||||
|
order.market?.positionDecimalPlaces as number
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{order.icebergOrder && (
|
||||||
|
<KeyValueTableRow
|
||||||
|
key={'order-iceberg-order-minimum-visible-size'}
|
||||||
|
className="ml-4"
|
||||||
|
>
|
||||||
|
<div data-testid={'order-iceberg-order-minimum-visible-size-label'}>
|
||||||
|
<Tooltip
|
||||||
|
description={t(
|
||||||
|
'When the order trades and its size falls below this threshold, it will be reset to the peak size and moved to the back of the priority order. Must be less than or equal to peak size, and greater than 0.'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{t('Minimum size')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div data-testid={`order-iceberg-order-minimum-visible-size-value`}>
|
||||||
|
<Size
|
||||||
|
value={order.icebergOrder.minimumVisibleSize}
|
||||||
|
side={order.side}
|
||||||
|
positionDecimalPlaces={
|
||||||
|
order.market?.positionDecimalPlaces as number
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
{order.icebergOrder && (
|
||||||
|
<KeyValueTableRow
|
||||||
|
key={'order-iceberg-order-reserved-remaining'}
|
||||||
|
className="ml-4"
|
||||||
|
>
|
||||||
|
<div data-testid={'order-iceberg-order-reserved-remaining-label'}>
|
||||||
|
{t('Reserved remaining')}
|
||||||
|
</div>
|
||||||
|
<div data-testid={`order-iceberg-order-reserved-remaining-value`}>
|
||||||
|
<Size
|
||||||
|
value={order.icebergOrder.reservedRemaining}
|
||||||
|
side={order.side}
|
||||||
|
positionDecimalPlaces={
|
||||||
|
order.market?.positionDecimalPlaces as number
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -16,7 +16,7 @@ interface DialogProps {
|
|||||||
title?: string | ReactNode;
|
title?: string | ReactNode;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
intent?: Intent;
|
intent?: Intent;
|
||||||
size?: 'small' | 'medium';
|
size?: 'small' | 'medium' | 'large';
|
||||||
dataTestId?: string;
|
dataTestId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,8 @@ export function Dialog({
|
|||||||
getIntentBorder(intent),
|
getIntentBorder(intent),
|
||||||
{
|
{
|
||||||
'w-[520px]': size === 'small',
|
'w-[520px]': size === 'small',
|
||||||
'w-[720px] lg:w-[940px]': size === 'medium',
|
'w-[680px]': size === 'medium',
|
||||||
|
'w-[720px] lg:w-[940px]': size === 'large',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -43,17 +43,18 @@ export const DropdownMenuTrigger = forwardRef<
|
|||||||
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
|
||||||
DropdownMenuPrimitive.DropdownMenuTriggerProps
|
DropdownMenuPrimitive.DropdownMenuTriggerProps
|
||||||
>(({ className, children, ...props }, forwardedRef) => {
|
>(({ className, children, ...props }, forwardedRef) => {
|
||||||
const defaultClasses = [
|
const triggerClasses = classNames(
|
||||||
'text-sm py-1 px-2 rounded bg-transparent border whitespace-nowrap',
|
'text-sm py-1 px-2 rounded bg-transparent border whitespace-nowrap',
|
||||||
'border-vega-light-200 dark:border-vega-dark-200',
|
'border-vega-light-200 dark:border-vega-dark-200',
|
||||||
'hover:border-vega-light-300 dark:hover:border-vega-dark-300',
|
'hover:border-vega-light-300 dark:hover:border-vega-dark-300',
|
||||||
].join(' ');
|
className
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Trigger
|
<DropdownMenuPrimitive.Trigger
|
||||||
asChild={true}
|
asChild={true}
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
className={className || defaultClasses}
|
className={triggerClasses}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<button>{children}</button>
|
<button>{children}</button>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export const IconEye = ({ size = 16 }: { size: number }) => {
|
||||||
|
return (
|
||||||
|
<svg width={size} height={size} viewBox="0 0 16 16">
|
||||||
|
<path d="M0.678395 7.44128C2.71173 4.52663 5.34817 2.99829 7.9894 2.99829C11.0914 2.99829 13.7635 5.01915 15.34 7.45404L15.3408 7.45519C15.4451 7.61729 15.5006 7.80599 15.5006 7.99876C15.5006 8.19118 15.4453 8.37954 15.3414 8.54144C13.7662 11.0077 11.1114 12.9983 7.9894 12.9983C4.8342 12.9983 2.23054 11.0118 0.659261 8.55277C0.552495 8.38698 0.497119 8.19332 0.500115 7.99613C0.503119 7.79845 0.564651 7.6061 0.676935 7.44338L0.67839 7.44127L0.678395 7.44128ZM1.5 8.01133L1.50139 8.01348L1.50139 8.01349C2.95267 10.2852 5.27875 11.9983 7.9894 11.9983C10.6697 11.9983 13.0459 10.278 14.4989 8.00262L14.4999 8.00113C14.5001 8.00073 14.5003 8.0003 14.5005 7.99985C14.5005 7.9995 14.5006 7.99913 14.5006 7.99876C14.5006 7.99792 14.5003 7.9971 14.4999 7.99639L14.9203 7.72579L14.5006 7.99754C13.0421 5.74493 10.6461 3.99829 7.9894 3.99829C5.75997 3.99829 3.40017 5.28866 1.5 8.01133Z" />
|
||||||
|
<path d="M8 5.99951C6.89543 5.99951 6 6.89494 6 7.99951C6 9.10408 6.89543 9.99951 8 9.99951C9.10457 9.99951 10 9.10408 10 7.99951C10 6.89494 9.10457 5.99951 8 5.99951ZM5 7.99951C5 6.34266 6.34315 4.99951 8 4.99951C9.65685 4.99951 11 6.34266 11 7.99951C11 9.65637 9.65685 10.9995 8 10.9995C6.34315 10.9995 5 9.65637 5 7.99951Z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -12,6 +12,7 @@ import { IconCross } from './svg-icons/icon-cross';
|
|||||||
import { IconDeposit } from './svg-icons/icon-deposit';
|
import { IconDeposit } from './svg-icons/icon-deposit';
|
||||||
import { IconEdit } from './svg-icons/icon-edit';
|
import { IconEdit } from './svg-icons/icon-edit';
|
||||||
import { IconExclaimationMark } from './svg-icons/icon-exclaimation-mark';
|
import { IconExclaimationMark } from './svg-icons/icon-exclaimation-mark';
|
||||||
|
import { IconEye } from './svg-icons/icon-eye';
|
||||||
import { IconForum } from './svg-icons/icon-forum';
|
import { IconForum } from './svg-icons/icon-forum';
|
||||||
import { IconGlobe } from './svg-icons/icon-globe';
|
import { IconGlobe } from './svg-icons/icon-globe';
|
||||||
import { IconInfo } from './svg-icons/icon-info';
|
import { IconInfo } from './svg-icons/icon-info';
|
||||||
@@ -48,6 +49,7 @@ export enum VegaIconNames {
|
|||||||
DEPOSIT = 'deposit',
|
DEPOSIT = 'deposit',
|
||||||
EDIT = 'edit',
|
EDIT = 'edit',
|
||||||
EXCLAIMATION_MARK = 'exclaimation-mark',
|
EXCLAIMATION_MARK = 'exclaimation-mark',
|
||||||
|
EYE = 'eye',
|
||||||
FORUM = 'forum',
|
FORUM = 'forum',
|
||||||
GLOBE = 'globe',
|
GLOBE = 'globe',
|
||||||
INFO = 'info',
|
INFO = 'info',
|
||||||
@@ -88,6 +90,7 @@ export const VegaIconNameMap: Record<
|
|||||||
deposit: IconDeposit,
|
deposit: IconDeposit,
|
||||||
edit: IconEdit,
|
edit: IconEdit,
|
||||||
'exclaimation-mark': IconExclaimationMark,
|
'exclaimation-mark': IconExclaimationMark,
|
||||||
|
eye: IconEye,
|
||||||
forum: IconForum,
|
forum: IconForum,
|
||||||
globe: IconGlobe,
|
globe: IconGlobe,
|
||||||
info: IconInfo,
|
info: IconInfo,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const NotificationBanner = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'flex items-center p-3 border-b min-h-[56px]',
|
'flex items-center px-1 py-3 border-b min-h-[56px]',
|
||||||
'text-[12px] leading-[16px] font-normal',
|
'text-[12px] leading-[16px] font-normal',
|
||||||
{
|
{
|
||||||
'bg-vega-light-100 dark:bg-vega-dark-100 ': intent === Intent.None,
|
'bg-vega-light-100 dark:bg-vega-dark-100 ': intent === Intent.None,
|
||||||
|
|||||||
@@ -30,4 +30,43 @@ describe('Tabs', () => {
|
|||||||
await userEvent.click(screen.getByText('Tab two'));
|
await userEvent.click(screen.getByText('Tab two'));
|
||||||
expect(await screen.getByText('Tab two content')).toBeInTheDocument();
|
expect(await screen.getByText('Tab two content')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows the menu component for the selected tab', async () => {
|
||||||
|
render(
|
||||||
|
<Tabs>
|
||||||
|
<Tab
|
||||||
|
id="one"
|
||||||
|
name="Tab one"
|
||||||
|
menu={
|
||||||
|
<>
|
||||||
|
<button>Tab 1 button 1</button>
|
||||||
|
<button>Tab 1 button 2</button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>Tab one content</p>
|
||||||
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
id="two"
|
||||||
|
name="Tab two"
|
||||||
|
menu={
|
||||||
|
<>
|
||||||
|
<button>Tab 2 button 1</button>
|
||||||
|
<button>Tab 2 button 2</button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>Tab two content</p>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Tab one content')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Tab 1 button 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Tab 1 button 2')).toBeInTheDocument();
|
||||||
|
await userEvent.click(screen.getByText('Tab two'));
|
||||||
|
expect(await screen.getByText('Tab two content')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Tab 2 button 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Tab 2 button 2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|||||||
import {
|
import {
|
||||||
useLocalStorageSnapshot,
|
useLocalStorageSnapshot,
|
||||||
getValidItem,
|
getValidItem,
|
||||||
|
useResizeObserver,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ReactElement, ReactNode } from 'react';
|
import type { ReactElement, ReactNode } from 'react';
|
||||||
import { Children, isValidElement, useState } from 'react';
|
import { Children, isValidElement, useRef, useState } from 'react';
|
||||||
export interface TabsProps extends TabsPrimitive.TabsProps {
|
export interface TabsProps extends TabsPrimitive.TabsProps {
|
||||||
children: (ReactElement<TabProps> | null)[];
|
children: (ReactElement<TabProps> | null)[];
|
||||||
}
|
}
|
||||||
@@ -24,6 +25,19 @@ export const Tabs = ({
|
|||||||
return children.find((v) => v)?.props.id;
|
return children.find((v) => v)?.props.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Bunch of refs in order to detect wrapping in side the tabs so that we
|
||||||
|
// can apply a bg color
|
||||||
|
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const tabsRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const menuRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [wrapped, setWrapped] = useState(() =>
|
||||||
|
isWrapped(tabsRef.current, menuRef.current)
|
||||||
|
);
|
||||||
|
|
||||||
|
useResizeObserver(wrapperRef.current, () => {
|
||||||
|
setWrapped(isWrapped(tabsRef.current, menuRef.current));
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabsPrimitive.Root
|
<TabsPrimitive.Root
|
||||||
{...props}
|
{...props}
|
||||||
@@ -31,10 +45,14 @@ export const Tabs = ({
|
|||||||
onValueChange={onValueChange || setActiveTab}
|
onValueChange={onValueChange || setActiveTab}
|
||||||
className="h-full grid grid-rows-[min-content_1fr]"
|
className="h-full grid grid-rows-[min-content_1fr]"
|
||||||
>
|
>
|
||||||
<div className="border-b border-default min-w-0">
|
<div
|
||||||
|
ref={wrapperRef}
|
||||||
|
className="flex flex-wrap justify-between border-b border-default min-w-0"
|
||||||
|
>
|
||||||
<TabsPrimitive.List
|
<TabsPrimitive.List
|
||||||
className="flex flex-nowrap overflow-visible"
|
className="flex flex-nowrap overflow-visible"
|
||||||
role="tablist"
|
role="tablist"
|
||||||
|
ref={tabsRef}
|
||||||
>
|
>
|
||||||
{Children.map(children, (child) => {
|
{Children.map(children, (child) => {
|
||||||
if (!isValidElement(child) || child.props.hidden) return null;
|
if (!isValidElement(child) || child.props.hidden) return null;
|
||||||
@@ -67,6 +85,27 @@ export const Tabs = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</TabsPrimitive.List>
|
</TabsPrimitive.List>
|
||||||
|
<div
|
||||||
|
ref={menuRef}
|
||||||
|
className={classNames('flex-1 p-1', {
|
||||||
|
'bg-vega-clight-700 dark:bg-vega-cdark-700': wrapped,
|
||||||
|
'': wrapped,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{Children.map(children, (child) => {
|
||||||
|
if (!isValidElement(child) || child.props.hidden) return null;
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
value={child.props.id}
|
||||||
|
className={classNames('flex flex-nowrap gap-1', {
|
||||||
|
'justify-end': !wrapped,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{child.props.menu}
|
||||||
|
</TabsPrimitive.Content>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full overflow-auto">
|
<div className="h-full overflow-auto">
|
||||||
{Children.map(children, (child) => {
|
{Children.map(children, (child) => {
|
||||||
@@ -92,6 +131,7 @@ interface TabProps {
|
|||||||
name: string;
|
name: string;
|
||||||
indicator?: ReactNode;
|
indicator?: ReactNode;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
|
menu?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Tab = ({ children, ...props }: TabProps) => {
|
export const Tab = ({ children, ...props }: TabProps) => {
|
||||||
@@ -122,3 +162,13 @@ export const LocalStoragePersistTabs = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isWrapped = (
|
||||||
|
tabs: HTMLDivElement | null,
|
||||||
|
menu: HTMLDivElement | null
|
||||||
|
) => {
|
||||||
|
if (!tabs || !menu) return;
|
||||||
|
const listRect = tabs.getBoundingClientRect();
|
||||||
|
const menuRect = menu.getBoundingClientRect();
|
||||||
|
return menuRect.y > listRect.y;
|
||||||
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type {
|
|||||||
import { Intent } from '../../utils/intent';
|
import { Intent } from '../../utils/intent';
|
||||||
|
|
||||||
type TradingButtonProps = {
|
type TradingButtonProps = {
|
||||||
size?: 'large' | 'medium' | 'small';
|
size?: 'large' | 'medium' | 'small' | 'extra-small';
|
||||||
intent?: Intent;
|
intent?: Intent;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
@@ -24,24 +24,31 @@ const getClassName = (
|
|||||||
className?: string
|
className?: string
|
||||||
) =>
|
) =>
|
||||||
classNames(
|
classNames(
|
||||||
'flex gap-2 items-center justify-center rounded',
|
'flex gap-2 items-center justify-center rounded disabled:opacity-40',
|
||||||
// size
|
// size
|
||||||
{
|
{
|
||||||
'h-12': !subLabel && size === 'large',
|
'h-12': !subLabel && size === 'large',
|
||||||
'h-10': !subLabel && (!size || size === 'medium'),
|
'h-10': !subLabel && (!size || size === 'medium'),
|
||||||
'h-8': !subLabel && size === 'small',
|
'h-8': !subLabel && size === 'small',
|
||||||
'px-3 text-sm': !subLabel && size === 'small',
|
'px-3 text-sm': !subLabel && size === 'small',
|
||||||
|
'h-6 px-2 text-xs': !subLabel && size === 'extra-small',
|
||||||
'px-4 text-base': !subLabel && size !== 'small',
|
'px-4 text-base': !subLabel && size !== 'small',
|
||||||
'flex-col items-center justify-center px-3 pt-2.5 pb-2': subLabel,
|
'flex-col items-center justify-center px-3 pt-2.5 pb-2': subLabel,
|
||||||
},
|
},
|
||||||
// colours
|
// colours
|
||||||
{
|
{
|
||||||
'bg-vega-yellow dark:bg-vega-yellow': intent === Intent.Primary,
|
'bg-vega-yellow hover:bg-vega-yellow-550 dark:bg-vega-yellow dark:hover:bg-vega-yellow-450':
|
||||||
'bg-vega-clight-500 dark:bg-vega-cdark-500': intent === Intent.None,
|
intent === Intent.Primary,
|
||||||
'bg-vega-blue-350 dark:bg-vega-blue-650': intent === Intent.Info,
|
'bg-vega-clight-500 hover:bg-vega-clight-400 dark:bg-vega-cdark-500 dark:hover:bg-vega-cdark-400':
|
||||||
'bg-vega-orange-350 dark:bg-vega-orange-650': intent === Intent.Warning,
|
intent === Intent.None,
|
||||||
'bg-vega-red-350 dark:bg-vega-red-650': intent === Intent.Danger,
|
'bg-vega-blue-350 hover:bg-vega-blue-400 dark:bg-vega-blue-650 dark:hover:bg-vega-blue-600':
|
||||||
'bg-vega-green-350 dark:bg-vega-green-650': intent === Intent.Success,
|
intent === Intent.Info,
|
||||||
|
'bg-vega-orange-350 hover:bg-vega-orange-400 dark:bg-vega-orange-650 dark:hover:bg-vega-orange-600':
|
||||||
|
intent === Intent.Warning,
|
||||||
|
'bg-vega-red-350 hover:bg-vega-red-400 dark:bg-vega-red-650 dark:hover:bg-vega-red-600':
|
||||||
|
intent === Intent.Danger,
|
||||||
|
'bg-vega-green-350 hover:bg-vega-green-400 dark:bg-vega-green-650 dark:hover:bg-vega-green-600':
|
||||||
|
intent === Intent.Success,
|
||||||
'text-vega-clight-50 dark:text-vega-cdark-50': intent !== Intent.Primary,
|
'text-vega-clight-50 dark:text-vega-cdark-50': intent !== Intent.Primary,
|
||||||
'text-vega-clight-900 dark:text-vega-cdark-900':
|
'text-vega-clight-900 dark:text-vega-cdark-900':
|
||||||
intent === Intent.Primary,
|
intent === Intent.Primary,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { NotificationBanner } from '../notification-banner';
|
import { NotificationBanner } from '../notification-banner';
|
||||||
import { Intent } from '../../utils/intent';
|
import { Intent } from '../../utils/intent';
|
||||||
|
import { TradingButton } from '../trading-button';
|
||||||
|
|
||||||
export function truncateMiddle(address: string, start = 6, end = 4) {
|
export function truncateMiddle(address: string, start = 6, end = 4) {
|
||||||
if (address.length < 11) return address;
|
if (address.length < 11) return address;
|
||||||
@@ -24,19 +25,20 @@ export const ViewingAsBanner = ({
|
|||||||
<NotificationBanner
|
<NotificationBanner
|
||||||
data-testid="view-banner"
|
data-testid="view-banner"
|
||||||
intent={Intent.None}
|
intent={Intent.None}
|
||||||
className="py-2 min-h-fit"
|
className="!px-1 !py-1 min-h-fit"
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-baseline">
|
<div className="flex justify-between items-baseline">
|
||||||
<span>
|
<span>
|
||||||
{t('Viewing as Vega user:')} {pubKey && truncateMiddle(pubKey)}{' '}
|
{t('Viewing as Vega user:')} {pubKey && truncateMiddle(pubKey)}{' '}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<TradingButton
|
||||||
className="p-2 bg-light-dark-150 dark:bg-vega-dark-150 rounded uppercase"
|
intent={Intent.None}
|
||||||
|
size="extra-small"
|
||||||
data-testid="exit-view"
|
data-testid="exit-view"
|
||||||
onClick={disconnect}
|
onClick={disconnect}
|
||||||
>
|
>
|
||||||
{t('Exit view as')}
|
{t('Exit view as')}
|
||||||
</button>
|
</TradingButton>
|
||||||
</div>
|
</div>
|
||||||
</NotificationBanner>
|
</NotificationBanner>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from './connect-dialog';
|
export * from './connect-dialog';
|
||||||
|
export * from './view-as-dialog';
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { Dialog } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { ViewConnectorForm } from './view-connector-form';
|
||||||
|
import type { ViewConnector } from '../connectors';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
type ViewAsDialogStore = {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (open: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useViewAsDialog = create<ViewAsDialogStore>()((set) => ({
|
||||||
|
open: false,
|
||||||
|
setOpen: (open) => set({ open }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
type ViewAsDialogProps = {
|
||||||
|
connector: ViewConnector;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewAsDialog = ({ connector }: ViewAsDialogProps) => {
|
||||||
|
const open = useViewAsDialog((state) => state.open);
|
||||||
|
const setOpen = useViewAsDialog((state) => state.setOpen);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
size="small"
|
||||||
|
onChange={(open) => {
|
||||||
|
setOpen(open);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ViewConnectorForm
|
||||||
|
connector={connector}
|
||||||
|
onConnect={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,9 +2,10 @@ import { t } from '@vegaprotocol/i18n';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Icon,
|
|
||||||
Input,
|
Input,
|
||||||
InputError,
|
InputError,
|
||||||
|
VegaIcon,
|
||||||
|
VegaIconNames,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import type { ViewConnector } from '../connectors';
|
import type { ViewConnector } from '../connectors';
|
||||||
@@ -17,7 +18,7 @@ interface FormFields {
|
|||||||
interface RestConnectorFormProps {
|
interface RestConnectorFormProps {
|
||||||
connector: ViewConnector;
|
connector: ViewConnector;
|
||||||
onConnect: (connector: ViewConnector) => void;
|
onConnect: (connector: ViewConnector) => void;
|
||||||
reset: () => void;
|
reset?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ViewConnectorForm({
|
export function ViewConnectorForm({
|
||||||
@@ -50,13 +51,19 @@ export function ViewConnectorForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
{reset && (
|
||||||
onClick={reset}
|
<button
|
||||||
className="absolute p-2 top-0 left-0 md:top-2 md:left-2"
|
onClick={reset}
|
||||||
data-testid="back-button"
|
className="absolute p-2 top-0 left-0 md:top-2 md:left-2"
|
||||||
>
|
data-testid="back-button"
|
||||||
<Icon name={'chevron-left'} ariaLabel="back" size={4} />
|
>
|
||||||
</button>
|
<VegaIcon
|
||||||
|
name={VegaIconNames.CHEVRON_LEFT}
|
||||||
|
aria-label="back"
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<form onSubmit={handleSubmit(onSubmit)} data-testid="view-connector-form">
|
<form onSubmit={handleSubmit(onSubmit)} data-testid="view-connector-form">
|
||||||
<h1 className="text-2xl uppercase mb-6 text-center font-alpha calt">
|
<h1 className="text-2xl uppercase mb-6 text-center font-alpha calt">
|
||||||
{t('VIEW AS VEGA USER')}
|
{t('VIEW AS VEGA USER')}
|
||||||
|
|||||||
@@ -12,5 +12,7 @@ export * from './vega-transaction-dialog';
|
|||||||
export * from './provider';
|
export * from './provider';
|
||||||
export * from './connect-dialog';
|
export * from './connect-dialog';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
export * from './storage';
|
||||||
|
export * from './is-browser-wallet-installed';
|
||||||
export * from './__generated__/TransactionResult';
|
export * from './__generated__/TransactionResult';
|
||||||
export * from './__generated__/WithdrawalApproval';
|
export * from './__generated__/WithdrawalApproval';
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const isBrowserWalletInstalled = () => Boolean(window.vega);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user