Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc758e90c8 | ||
|
|
a70a7bcc8e | ||
|
|
4461a6a497 | ||
|
|
1041864ad8 | ||
|
|
015e0188ec | ||
|
|
228236c152 | ||
|
|
cd6e906dc6 | ||
|
|
8a47a92dbd | ||
|
|
af14c27df9 | ||
|
|
38edcd88c0 | ||
|
|
a74cf02030 | ||
|
|
6686755763 | ||
|
|
79cf8163be | ||
|
|
1688e26f4b | ||
|
|
5e101a8eaf | ||
|
|
65071f57d0 | ||
|
|
b87c981a90 | ||
|
|
b509e5b347 | ||
|
|
bb9fce4831 | ||
|
|
918825aca8 | ||
|
|
74bf183fad | ||
|
|
674baa1ee3 | ||
|
|
d18a0c7e15 | ||
|
|
76a6005b77 | ||
|
|
79414614c2 | ||
|
|
fde49e5446 | ||
|
|
45283161aa | ||
|
|
375d447fa8 | ||
|
|
1dd97a2bce |
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
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: '
|
||||||
|
labels: feature-epic
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Feature
|
||||||
|
|
||||||
|
<Name>
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
- [ ] Define high level requirements
|
||||||
|
- [ ] Create basic mockups
|
||||||
|
- [ ] Update "API Requirements" in core spec
|
||||||
|
- [ ] Update "User-Interface Spec" in relevant front end repo
|
||||||
|
- [ ] Create detailed user stories using normal template
|
||||||
|
|
||||||
|
## High Level Requirements
|
||||||
|
|
||||||
|
## Basic Mockups
|
||||||
|
|
||||||
|
## Link to API Requirements in Core spec
|
||||||
|
|
||||||
|
## Link to User Interface Specs
|
||||||
|
|
||||||
|
## Linked User Stories
|
||||||
@@ -29,6 +29,12 @@ jobs:
|
|||||||
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
|
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
|
||||||
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
|
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
|
||||||
echo IS_S3_RELEASE=false >> $GITHUB_ENV
|
echo IS_S3_RELEASE=false >> $GITHUB_ENV
|
||||||
|
echo IS_DEV_IMAGE=false >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Is dev image
|
||||||
|
if: ${{ contains(github.ref, 'develop') && github.event_name == 'push' && matrix.app == 'trading' }}
|
||||||
|
run: |
|
||||||
|
echo IS_DEV_IMAGE=true >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Is PR
|
- name: Is PR
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
@@ -75,7 +81,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Log in to the Container registry (docker hub)
|
- name: Log in to the Container registry (docker hub)
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@@ -173,7 +179,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
id: dockerhub-push
|
id: dockerhub-push
|
||||||
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
|
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/node-outside-docker.Dockerfile
|
file: docker/node-outside-docker.Dockerfile
|
||||||
@@ -183,7 +189,7 @@ jobs:
|
|||||||
ENV_NAME=${{ env.ENV_NAME }}
|
ENV_NAME=${{ env.ENV_NAME }}
|
||||||
tags: |
|
tags: |
|
||||||
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
|
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
|
||||||
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
|
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || '' }}
|
||||||
|
|
||||||
- name: Publish dist as docker image (ghcr - retry)
|
- name: Publish dist as docker image (ghcr - retry)
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
@@ -100,7 +101,6 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
|||||||
'VEGA_TOKEN_URL'
|
'VEGA_TOKEN_URL'
|
||||||
)}/proposals/e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829`
|
)}/proposals/e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829`
|
||||||
);
|
);
|
||||||
cy.getByTestId('proposal-actions-content').click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 6001-MARK-060
|
// 6001-MARK-060
|
||||||
@@ -214,11 +214,13 @@ describe('no markets proposed', { tags: '@smoke', testIsolation: true }, () => {
|
|||||||
aliasGQLQuery(req, 'ProposalsList', proposal);
|
aliasGQLQuery(req, 'ProposalsList', proposal);
|
||||||
});
|
});
|
||||||
cy.mockSubscription();
|
cy.mockSubscription();
|
||||||
cy.visit('/#/markets/all');
|
cy.setOnBoardingViewed();
|
||||||
cy.get('[data-testid="Proposed markets"]').click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can see no markets message', () => {
|
it('can see no markets message', () => {
|
||||||
|
cy.visit('/#/markets/all');
|
||||||
|
cy.get('[data-testid="Proposed markets"]').click();
|
||||||
|
|
||||||
// 6001-MARK-061
|
// 6001-MARK-061
|
||||||
cy.getByTestId('tab-proposed-markets').should('contain.text', 'No markets');
|
cy.getByTestId('tab-proposed-markets').should('contain.text', 'No markets');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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');
|
||||||
});
|
});
|
||||||
@@ -21,37 +22,36 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
|||||||
it('must show place order button and connect wallet if wallet is not connected', () => {
|
it('must show place order button and connect wallet if wallet is not connected', () => {
|
||||||
// 0003-WTXN-001
|
// 0003-WTXN-001
|
||||||
cy.getByTestId('connect-vega-wallet'); // Not connected
|
cy.getByTestId('connect-vega-wallet'); // Not connected
|
||||||
cy.getByTestId('order-connect-wallet').should('exist');
|
|
||||||
cy.getByTestId(placeOrderBtn).should('exist');
|
cy.getByTestId(placeOrderBtn).should('exist');
|
||||||
cy.getByTestId('deal-ticket-connect-wallet').should('exist');
|
cy.getByTestId('get-started-button').should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('must be able to select order direction - long/short', function () {
|
it('must be able to select order direction - long/short', function () {
|
||||||
// 7002-SORD-004
|
// 7002-SORD-004
|
||||||
cy.getByTestId(toggleShort).click().children('input').should('be.checked');
|
cy.getByTestId(toggleShort).click().next('input').should('be.checked');
|
||||||
cy.getByTestId(toggleLong).click().children('input').should('be.checked');
|
cy.getByTestId(toggleLong).click().next('input').should('be.checked');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('must be able to select order type - limit/market', function () {
|
it('must be able to select order type - limit/market', function () {
|
||||||
// 7002-SORD-005
|
// 7002-SORD-005
|
||||||
// 7002-SORD-006
|
// 7002-SORD-006
|
||||||
// 7002-SORD-007
|
// 7002-SORD-007
|
||||||
cy.getByTestId(toggleLimit).click().children('input').should('be.checked');
|
cy.getByTestId(toggleLimit).click().next('input').should('be.checked');
|
||||||
cy.getByTestId(toggleMarket).click().children('input').should('be.checked');
|
cy.getByTestId(toggleMarket).click().next('input').should('be.checked');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('order connect vega wallet button should connect', () => {
|
it('order connect vega wallet button should connect', () => {
|
||||||
mockConnectWallet();
|
mockConnectWallet();
|
||||||
cy.getByTestId(toggleLimit).click();
|
cy.getByTestId(toggleLimit).click();
|
||||||
cy.getByTestId(orderPriceField).clear().type('101');
|
cy.getByTestId(orderPriceField).clear().type('101');
|
||||||
cy.getByTestId('order-connect-wallet').click();
|
cy.getByTestId('get-started-button').click();
|
||||||
cy.getByTestId('dialog-content').should('be.visible');
|
cy.getByTestId('dialog-content').should('be.visible');
|
||||||
cy.getByTestId('connectors-list')
|
cy.getByTestId('connectors-list')
|
||||||
.find('[data-testid="connector-jsonRpc"]')
|
.find('[data-testid="connector-jsonRpc"]')
|
||||||
.click();
|
.click();
|
||||||
cy.wait('@walletReq');
|
cy.wait('@walletReq');
|
||||||
cy.getByTestId(placeOrderBtn).should('be.visible');
|
cy.getByTestId(placeOrderBtn).should('be.visible');
|
||||||
cy.getByTestId(toggleLimit).children('input').should('be.checked');
|
cy.getByTestId(toggleLimit).next('input').should('be.checked');
|
||||||
cy.getByTestId(orderPriceField).should('have.value', '101');
|
cy.getByTestId(orderPriceField).should('have.value', '101');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,205 +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.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();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { OrderType } from '@vegaprotocol/types';
|
||||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
const orderSizeField = 'order-size';
|
const orderSizeField = 'order-size';
|
||||||
@@ -9,7 +10,9 @@ export const createOrder = (order: OrderSubmission): void => {
|
|||||||
cy.log('Placing order', order);
|
cy.log('Placing order', order);
|
||||||
const { type, side, size, price, timeInForce, expiresAt } = order;
|
const { type, side, size, price, timeInForce, expiresAt } = order;
|
||||||
|
|
||||||
cy.getByTestId(`order-type-${type}`).click();
|
cy.getByTestId(
|
||||||
|
`order-type-${type === OrderType.TYPE_LIMIT ? 'Limit' : 'Market'}`
|
||||||
|
).click();
|
||||||
cy.getByTestId(`order-side-${side}`).click();
|
cy.getByTestId(`order-side-${side}`).click();
|
||||||
cy.getByTestId(orderSizeField).clear().type(size);
|
cy.getByTestId(orderSizeField).clear().type(size);
|
||||||
if (price) {
|
if (price) {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export const orderTIFDropDown = 'order-tif';
|
|||||||
export const placeOrderBtn = 'place-order';
|
export const placeOrderBtn = 'place-order';
|
||||||
export const toggleShort = 'order-side-SIDE_SELL';
|
export const toggleShort = 'order-side-SIDE_SELL';
|
||||||
export const toggleLong = 'order-side-SIDE_BUY';
|
export const toggleLong = 'order-side-SIDE_BUY';
|
||||||
export const toggleLimit = 'order-type-TYPE_LIMIT';
|
export const toggleLimit = 'order-type-Limit';
|
||||||
export const toggleMarket = 'order-type-TYPE_MARKET';
|
export const toggleMarket = 'order-type-Market';
|
||||||
|
|
||||||
export const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
|
export const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
+1
-1
@@ -16,6 +16,6 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
# NX_STOP_ORDERS
|
NX_STOP_ORDERS=true
|
||||||
# NX_ICEBERG_ORDERS
|
# NX_ICEBERG_ORDERS
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
@@ -18,6 +18,6 @@ NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supp
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=false
|
NX_SUCCESSOR_MARKETS=false
|
||||||
# NX_STOP_ORDERS
|
NX_STOP_ORDERS=false
|
||||||
# NX_ICEBERG_ORDERS
|
# NX_ICEBERG_ORDERS
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
@@ -16,6 +16,6 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
# NX_STOP_ORDERS
|
NX_STOP_ORDERS=true
|
||||||
# NX_ICEBERG_ORDERS
|
# NX_ICEBERG_ORDERS
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
@@ -18,6 +18,6 @@ NX_APP_VERSION=v0.20.21-core-0.71.6
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=false
|
NX_SUCCESSOR_MARKETS=false
|
||||||
# NX_STOP_ORDERS
|
NX_STOP_ORDERS=false
|
||||||
# NX_ICEBERG_ORDERS
|
# NX_ICEBERG_ORDERS
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ NX_APP_VERSION=v0.20.19-core-0.71.6
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=false
|
NX_SUCCESSOR_MARKETS=false
|
||||||
# NX_STOP_ORDERS
|
NX_STOP_ORDERS=false
|
||||||
# NX_ICEBERG_ORDERS
|
# NX_ICEBERG_ORDERS
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
@@ -16,6 +16,6 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
# NX_STOP_ORDERS
|
NX_STOP_ORDERS=true
|
||||||
# NX_ICEBERG_ORDERS
|
# NX_ICEBERG_ORDERS
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
@@ -17,6 +17,6 @@ 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
|
NX_STOP_ORDERS=true
|
||||||
# NX_ICEBERG_ORDERS
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
@@ -17,6 +17,6 @@ NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks
|
|||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=false
|
NX_SUCCESSOR_MARKETS=false
|
||||||
# NX_STOP_ORDERS
|
NX_STOP_ORDERS=false
|
||||||
# NX_ICEBERG_ORDERS
|
# NX_ICEBERG_ORDERS
|
||||||
# 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,57 +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 ? (
|
||||||
|
<Tab id="stop-orders" name={t('Stop orders')}>
|
||||||
|
<TradingViews.stopOrders.component />
|
||||||
|
</Tab>
|
||||||
|
) : 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';
|
||||||
@@ -12,6 +15,7 @@ import { AccountsContainer } from '../../components/accounts-container';
|
|||||||
import { LiquidityContainer } from '../../components/liquidity-container';
|
import { LiquidityContainer } from '../../components/liquidity-container';
|
||||||
import type { OrderContainerProps } from '../../components/orders-container';
|
import type { OrderContainerProps } from '../../components/orders-container';
|
||||||
import { OrdersContainer } from '../../components/orders-container';
|
import { OrdersContainer } from '../../components/orders-container';
|
||||||
|
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||||
|
|
||||||
type MarketDependantView =
|
type MarketDependantView =
|
||||||
| typeof CandlesChartContainer
|
| typeof CandlesChartContainer
|
||||||
@@ -34,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',
|
||||||
@@ -74,6 +79,10 @@ export const TradingViews = {
|
|||||||
label: 'All',
|
label: 'All',
|
||||||
component: OrdersContainer,
|
component: OrdersContainer,
|
||||||
},
|
},
|
||||||
|
stopOrders: {
|
||||||
|
label: 'Stop',
|
||||||
|
component: StopOrdersContainer,
|
||||||
|
},
|
||||||
collateral: { label: 'Collateral', component: AccountsContainer },
|
collateral: { label: 'Collateral', component: AccountsContainer },
|
||||||
fills: { label: 'Fills', component: FillsContainer },
|
fills: { label: 'Fills', component: FillsContainer },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,15 +1,12 @@
|
|||||||
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';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { useRef } from 'react';
|
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
|
||||||
import { useSidebar, ViewType } from '../../components/sidebar';
|
import { useSidebar, ViewType } from '../../components/sidebar';
|
||||||
|
|
||||||
export const DepositsContainer = () => {
|
export const DepositsContainer = () => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
|
||||||
const { pubKey, isReadOnly } = useVegaWallet();
|
const { pubKey, isReadOnly } = useVegaWallet();
|
||||||
const { data, error } = useDataProvider({
|
const { data, error } = useDataProvider({
|
||||||
dataProvider: depositsProvider,
|
dataProvider: depositsProvider,
|
||||||
@@ -17,11 +14,13 @@ 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
|
||||||
rowData={data}
|
rowData={data}
|
||||||
ref={gridRef}
|
|
||||||
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
||||||
/>
|
/>
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
|
|||||||
@@ -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,10 +1,14 @@
|
|||||||
import { OrderbookManager } from '@vegaprotocol/market-depth';
|
import { OrderbookManager } from '@vegaprotocol/market-depth';
|
||||||
import { useCreateOrderStore } from '@vegaprotocol/orders';
|
import { useCreateOrderStore } from '@vegaprotocol/orders';
|
||||||
import { ViewType, useSidebar } from '../sidebar';
|
import { ViewType, useSidebar } from '../sidebar';
|
||||||
|
import { useStopOrderFormValues } from '@vegaprotocol/deal-ticket';
|
||||||
|
|
||||||
export const OrderbookContainer = ({ marketId }: { marketId: string }) => {
|
export const OrderbookContainer = ({ marketId }: { marketId: string }) => {
|
||||||
const useOrderStoreRef = useCreateOrderStore();
|
const useOrderStoreRef = useCreateOrderStore();
|
||||||
const updateOrder = useOrderStoreRef((store) => store.update);
|
const updateOrder = useOrderStoreRef((store) => store.update);
|
||||||
|
const updateStoredFormValues = useStopOrderFormValues(
|
||||||
|
(state) => state.update
|
||||||
|
);
|
||||||
const setView = useSidebar((store) => store.setView);
|
const setView = useSidebar((store) => store.setView);
|
||||||
return (
|
return (
|
||||||
<OrderbookManager
|
<OrderbookManager
|
||||||
@@ -12,9 +16,11 @@ export const OrderbookContainer = ({ marketId }: { marketId: string }) => {
|
|||||||
onClick={({ price, size }) => {
|
onClick={({ price, size }) => {
|
||||||
if (price) {
|
if (price) {
|
||||||
updateOrder(marketId, { price });
|
updateOrder(marketId, { price });
|
||||||
|
updateStoredFormValues(marketId, { price });
|
||||||
}
|
}
|
||||||
if (size) {
|
if (size) {
|
||||||
updateOrder(marketId, { size });
|
updateOrder(marketId, { size });
|
||||||
|
updateStoredFormValues(marketId, { size });
|
||||||
}
|
}
|
||||||
setView({ type: ViewType.Order });
|
setView({ type: ViewType.Order });
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './stop-orders-container';
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { StopOrdersManager } from '@vegaprotocol/orders';
|
||||||
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { persist } from 'zustand/middleware';
|
||||||
|
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
|
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||||
|
|
||||||
|
export const StopOrdersContainer = () => {
|
||||||
|
const { pubKey, isReadOnly } = useVegaWallet();
|
||||||
|
const onMarketClick = useMarketClickHandler(true);
|
||||||
|
|
||||||
|
const gridStore = useStopOrdersStore((store) => store.gridStore);
|
||||||
|
const updateGridStore = useStopOrdersStore((store) => store.updateGridStore);
|
||||||
|
|
||||||
|
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
|
||||||
|
updateGridStore(colState);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!pubKey) {
|
||||||
|
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StopOrdersManager
|
||||||
|
partyId={pubKey}
|
||||||
|
onMarketClick={onMarketClick}
|
||||||
|
isReadOnly={isReadOnly}
|
||||||
|
gridProps={gridStoreCallbacks}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStopOrdersStore = create<DataGridSlice>()(
|
||||||
|
persist(createDataGridSlice, {
|
||||||
|
name: 'vega_fills_store',
|
||||||
|
})
|
||||||
|
);
|
||||||
+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,108 @@
|
|||||||
|
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');
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapperClasses = classNames(
|
||||||
|
'flex flex-col py-4 px-6 gap-4 rounded',
|
||||||
|
'bg-vega-blue-300 dark:bg-vega-blue-700',
|
||||||
|
'border border-vega-blue-350 dark:border-vega-blue-650',
|
||||||
|
{ 'mt-8': !lead }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isBrowserWalletInstalled()) {
|
||||||
|
return (
|
||||||
|
<div className={wrapperClasses} data-testid="get-started-banner">
|
||||||
|
{lead && <h2>{lead}</h2>}
|
||||||
|
<h3 className="text-lg">{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}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pubKey) {
|
||||||
|
return (
|
||||||
|
<div className={wrapperClasses}>
|
||||||
|
<p className="text-sm mb-1">
|
||||||
|
You need a{' '}
|
||||||
|
<ExternalLink href="https://vega.xyz/wallet">
|
||||||
|
Vega wallet
|
||||||
|
</ExternalLink>{' '}
|
||||||
|
to start trading in this market.
|
||||||
|
</p>
|
||||||
|
<TradingButton
|
||||||
|
onClick={openVegaWalletDialog}
|
||||||
|
size="small"
|
||||||
|
data-testid="order-connect-wallet"
|
||||||
|
intent={Intent.Info}
|
||||||
|
>
|
||||||
|
{t('Connect wallet')}
|
||||||
|
</TradingButton>
|
||||||
|
</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-custodial and pseudonymous')}
|
||||||
|
text={t('No third party has access to your funds.')}
|
||||||
|
/>
|
||||||
|
<ListItemContent
|
||||||
|
icon={<PurposeBuiltIcon />}
|
||||||
|
title={t('Purpose built proof of stake blockchain')}
|
||||||
|
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) {
|
||||||
@@ -118,7 +118,6 @@ export const AccountManager = ({
|
|||||||
onMarketClick,
|
onMarketClick,
|
||||||
gridProps,
|
gridProps,
|
||||||
}: AccountManagerProps) => {
|
}: AccountManagerProps) => {
|
||||||
const gridRef = useRef<AgGridReact | null>(null);
|
|
||||||
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
|
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
|
||||||
const { data, error } = useDataProvider({
|
const { data, error } = useDataProvider({
|
||||||
dataProvider: aggregatedAccountsDataProvider,
|
dataProvider: aggregatedAccountsDataProvider,
|
||||||
@@ -138,7 +137,6 @@ export const AccountManager = ({
|
|||||||
return (
|
return (
|
||||||
<div className="relative h-full">
|
<div className="relative h-full">
|
||||||
<AccountTable
|
<AccountTable
|
||||||
ref={gridRef}
|
|
||||||
rowData={data}
|
rowData={data}
|
||||||
onClickAsset={onClickAsset}
|
onClickAsset={onClickAsset}
|
||||||
onClickDeposit={onClickDeposit}
|
onClickDeposit={onClickDeposit}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { forwardRef, useMemo, useCallback } from 'react';
|
import { useMemo, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
addDecimalsFormatNumberQuantum,
|
addDecimalsFormatNumberQuantum,
|
||||||
@@ -21,7 +21,7 @@ import type {
|
|||||||
RowHeightParams,
|
RowHeightParams,
|
||||||
ColDef,
|
ColDef,
|
||||||
} from 'ag-grid-community';
|
} from 'ag-grid-community';
|
||||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
import type { AgGridReactProps } from 'ag-grid-react';
|
||||||
import type { AccountFields } from './accounts-data-provider';
|
import type { AccountFields } from './accounts-data-provider';
|
||||||
import type { Asset } from '@vegaprotocol/types';
|
import type { Asset } from '@vegaprotocol/types';
|
||||||
import { CenteredGridCellWrapper } from '@vegaprotocol/datagrid';
|
import { CenteredGridCellWrapper } from '@vegaprotocol/datagrid';
|
||||||
@@ -75,256 +75,250 @@ export interface AccountTableProps extends AgGridReactProps {
|
|||||||
pinnedAsset?: PinnedAsset;
|
pinnedAsset?: PinnedAsset;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
export const AccountTable = ({
|
||||||
(
|
onClickAsset,
|
||||||
{
|
onClickWithdraw,
|
||||||
onClickAsset,
|
onClickDeposit,
|
||||||
onClickWithdraw,
|
onClickBreakdown,
|
||||||
onClickDeposit,
|
onClickTransfer,
|
||||||
onClickBreakdown,
|
rowData,
|
||||||
onClickTransfer,
|
isReadOnly,
|
||||||
rowData,
|
pinnedAsset,
|
||||||
isReadOnly,
|
...props
|
||||||
pinnedAsset,
|
}: AccountTableProps) => {
|
||||||
...props
|
const pinnedRow = useMemo(() => {
|
||||||
|
if (!pinnedAsset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentPinnedAssetRow = rowData?.find(
|
||||||
|
(row) => row.asset.id === pinnedAsset?.id
|
||||||
|
);
|
||||||
|
if (!currentPinnedAssetRow) {
|
||||||
|
return {
|
||||||
|
asset: pinnedAsset,
|
||||||
|
available: '0',
|
||||||
|
used: '0',
|
||||||
|
total: '0',
|
||||||
|
balance: '0',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return currentPinnedAssetRow;
|
||||||
|
}, [pinnedAsset, rowData]);
|
||||||
|
|
||||||
|
const { getRowHeight } = props;
|
||||||
|
|
||||||
|
const getPinnedAssetRowHeight = useCallback(
|
||||||
|
(params: RowHeightParams) => {
|
||||||
|
if (
|
||||||
|
params.node.rowPinned &&
|
||||||
|
params.data.asset.id === pinnedAsset?.id &&
|
||||||
|
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
||||||
|
) {
|
||||||
|
return 32;
|
||||||
|
}
|
||||||
|
return getRowHeight ? getRowHeight(params) : undefined;
|
||||||
},
|
},
|
||||||
ref
|
[pinnedAsset?.id, getRowHeight]
|
||||||
) => {
|
);
|
||||||
const pinnedRow = useMemo(() => {
|
|
||||||
if (!pinnedAsset) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const currentPinnedAssetRow = rowData?.find(
|
|
||||||
(row) => row.asset.id === pinnedAsset?.id
|
|
||||||
);
|
|
||||||
if (!currentPinnedAssetRow) {
|
|
||||||
return {
|
|
||||||
asset: pinnedAsset,
|
|
||||||
available: '0',
|
|
||||||
used: '0',
|
|
||||||
total: '0',
|
|
||||||
balance: '0',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return currentPinnedAssetRow;
|
|
||||||
}, [pinnedAsset, rowData]);
|
|
||||||
|
|
||||||
const { getRowHeight } = props;
|
const showDepositButton = pinnedRow?.balance === '0';
|
||||||
|
|
||||||
const getPinnedAssetRowHeight = useCallback(
|
const colDefs = useMemo(() => {
|
||||||
(params: RowHeightParams) => {
|
const defs: ColDef[] = [
|
||||||
if (
|
{
|
||||||
params.node.rowPinned &&
|
headerName: t('Asset'),
|
||||||
params.data.asset.id === pinnedAsset?.id &&
|
field: 'asset.symbol',
|
||||||
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
headerTooltip: t(
|
||||||
) {
|
'Asset is the collateral that is deposited into the Vega protocol.'
|
||||||
return 32;
|
),
|
||||||
}
|
cellClass: 'underline',
|
||||||
return getRowHeight ? getRowHeight(params) : undefined;
|
onCellClicked: ({ data }) => {
|
||||||
|
if (data) {
|
||||||
|
onClickAsset(data.asset.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
[pinnedAsset?.id, getRowHeight]
|
{
|
||||||
);
|
headerName: t('Used'),
|
||||||
|
type: 'rightAligned',
|
||||||
const showDepositButton = pinnedRow?.balance === '0';
|
field: 'used',
|
||||||
|
headerTooltip: t(
|
||||||
const colDefs = useMemo(() => {
|
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
|
||||||
const defs: ColDef[] = [
|
),
|
||||||
{
|
tooltipValueGetter: ({ value, data }) => {
|
||||||
headerName: t('Asset'),
|
if (!value || !data) return null;
|
||||||
field: 'asset.symbol',
|
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||||
headerTooltip: t(
|
|
||||||
'Asset is the collateral that is deposited into the Vega protocol.'
|
|
||||||
),
|
|
||||||
cellClass: 'underline',
|
|
||||||
onCellClicked: ({ data }) => {
|
|
||||||
if (data) {
|
|
||||||
onClickAsset(data.asset.id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
onCellClicked: ({ data }) => {
|
||||||
headerName: t('Used'),
|
if (!data || !onClickBreakdown) return;
|
||||||
type: 'rightAligned',
|
onClickBreakdown(data.asset.id);
|
||||||
field: 'used',
|
},
|
||||||
headerTooltip: t(
|
cellRenderer: ({
|
||||||
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
|
data,
|
||||||
),
|
value,
|
||||||
tooltipValueGetter: ({ value, data }) => {
|
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
||||||
if (!value || !data) return null;
|
if (!value || !data) return '-';
|
||||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
const percentageUsed = percentageValue(value, data.total);
|
||||||
},
|
const valueFormatted = addDecimalsFormatNumberQuantum(
|
||||||
onCellClicked: ({ data }) => {
|
|
||||||
if (!data || !onClickBreakdown) return;
|
|
||||||
onClickBreakdown(data.asset.id);
|
|
||||||
},
|
|
||||||
cellRenderer: ({
|
|
||||||
data,
|
|
||||||
value,
|
value,
|
||||||
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
data.asset.decimals,
|
||||||
if (!value || !data) return '-';
|
data.asset.quantum
|
||||||
const percentageUsed = percentageValue(value, data.total);
|
);
|
||||||
const valueFormatted = addDecimalsFormatNumberQuantum(
|
|
||||||
value,
|
|
||||||
data.asset.decimals,
|
|
||||||
data.asset.quantum
|
|
||||||
);
|
|
||||||
|
|
||||||
return data.breakdown ? (
|
return data.breakdown ? (
|
||||||
<>
|
<>
|
||||||
<span className="underline">{valueFormatted}</span>
|
<span className="underline">{valueFormatted}</span>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
colorClass(percentageUsed),
|
colorClass(percentageUsed),
|
||||||
'ml-1 inline-block w-14'
|
'ml-1 inline-block w-14'
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
|
{percentageUsed.toFixed(2)}%
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="underline">{valueFormatted}</span>
|
||||||
|
<span className="ml-2 inline-block w-14 text-muted">
|
||||||
|
{t('0.00%')}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: t('Available'),
|
||||||
|
field: 'available',
|
||||||
|
type: 'rightAligned',
|
||||||
|
headerTooltip: t(
|
||||||
|
'Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.'
|
||||||
|
),
|
||||||
|
tooltipValueGetter: ({ value, data }) => {
|
||||||
|
if (!value || !data) return null;
|
||||||
|
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||||
|
},
|
||||||
|
cellClass: ({ data }) => {
|
||||||
|
const percentageUsed = percentageValue(data?.used, data?.total);
|
||||||
|
return colorClass(percentageUsed, true);
|
||||||
|
},
|
||||||
|
valueFormatter: ({
|
||||||
|
value,
|
||||||
|
data,
|
||||||
|
}: VegaValueFormatterParams<AccountFields, 'available'>) => {
|
||||||
|
if (!value || !data) return '-';
|
||||||
|
return addDecimalsFormatNumberQuantum(
|
||||||
|
value,
|
||||||
|
data.asset.decimals,
|
||||||
|
data.asset.quantum
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
headerName: t('Total'),
|
||||||
|
type: 'rightAligned',
|
||||||
|
field: 'total',
|
||||||
|
headerTooltip: t(
|
||||||
|
'The total amount of each asset on this key. Includes used and available collateral.'
|
||||||
|
),
|
||||||
|
tooltipValueGetter: ({ value, data }) => {
|
||||||
|
if (!value || !data) return null;
|
||||||
|
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||||
|
},
|
||||||
|
valueFormatter: ({
|
||||||
|
data,
|
||||||
|
value,
|
||||||
|
}: VegaValueFormatterParams<AccountFields, 'total'>) => {
|
||||||
|
if (!data || !value) return '-';
|
||||||
|
return addDecimalsFormatNumberQuantum(
|
||||||
|
value,
|
||||||
|
data.asset.decimals,
|
||||||
|
data.asset.quantum
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colId: 'accounts-actions',
|
||||||
|
field: 'asset.id',
|
||||||
|
...COL_DEFS.actions,
|
||||||
|
minWidth: showDepositButton ? 130 : COL_DEFS.actions.minWidth,
|
||||||
|
maxWidth: showDepositButton ? 130 : COL_DEFS.actions.maxWidth,
|
||||||
|
cellRenderer: ({
|
||||||
|
value: assetId,
|
||||||
|
node,
|
||||||
|
}: VegaICellRendererParams<AccountFields, 'asset.id'>) => {
|
||||||
|
if (!assetId) return null;
|
||||||
|
if (node.rowPinned && node.data?.total === '0') {
|
||||||
|
return (
|
||||||
|
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="primary"
|
||||||
|
data-testid="deposit"
|
||||||
|
onClick={() => {
|
||||||
|
onClickDeposit && onClickDeposit(assetId);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{percentageUsed.toFixed(2)}%
|
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
||||||
</span>
|
</Button>
|
||||||
</>
|
</CenteredGridCellWrapper>
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="underline">{valueFormatted}</span>
|
|
||||||
<span className="ml-2 inline-block w-14 text-muted">
|
|
||||||
{t('0.00%')}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
return isReadOnly ? null : (
|
||||||
{
|
<AccountsActionsDropdown
|
||||||
headerName: t('Available'),
|
assetId={assetId}
|
||||||
field: 'available',
|
assetContractAddress={
|
||||||
type: 'rightAligned',
|
node.data?.asset.source?.__typename === 'ERC20'
|
||||||
headerTooltip: t(
|
? node.data.asset.source.contractAddress
|
||||||
'Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.'
|
: undefined
|
||||||
),
|
}
|
||||||
tooltipValueGetter: ({ value, data }) => {
|
onClickDeposit={() => {
|
||||||
if (!value || !data) return null;
|
onClickDeposit && onClickDeposit(assetId);
|
||||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
}}
|
||||||
},
|
onClickWithdraw={() => {
|
||||||
cellClass: ({ data }) => {
|
onClickWithdraw && onClickWithdraw(assetId);
|
||||||
const percentageUsed = percentageValue(data?.used, data?.total);
|
}}
|
||||||
return colorClass(percentageUsed, true);
|
onClickBreakdown={() => {
|
||||||
},
|
onClickBreakdown && onClickBreakdown(assetId);
|
||||||
valueFormatter: ({
|
}}
|
||||||
value,
|
onClickTransfer={() => {
|
||||||
data,
|
onClickTransfer && onClickTransfer(assetId);
|
||||||
}: VegaValueFormatterParams<AccountFields, 'available'>) => {
|
}}
|
||||||
if (!value || !data) return '-';
|
/>
|
||||||
return addDecimalsFormatNumberQuantum(
|
);
|
||||||
value,
|
|
||||||
data.asset.decimals,
|
|
||||||
data.asset.quantum
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return defs;
|
||||||
|
}, [
|
||||||
|
onClickAsset,
|
||||||
|
onClickBreakdown,
|
||||||
|
onClickDeposit,
|
||||||
|
onClickWithdraw,
|
||||||
|
onClickTransfer,
|
||||||
|
isReadOnly,
|
||||||
|
showDepositButton,
|
||||||
|
]);
|
||||||
|
|
||||||
{
|
const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);
|
||||||
headerName: t('Total'),
|
|
||||||
type: 'rightAligned',
|
|
||||||
field: 'total',
|
|
||||||
headerTooltip: t(
|
|
||||||
'The total amount of each asset on this key. Includes used and available collateral.'
|
|
||||||
),
|
|
||||||
tooltipValueGetter: ({ value, data }) => {
|
|
||||||
if (!value || !data) return null;
|
|
||||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
|
||||||
},
|
|
||||||
valueFormatter: ({
|
|
||||||
data,
|
|
||||||
value,
|
|
||||||
}: VegaValueFormatterParams<AccountFields, 'total'>) => {
|
|
||||||
if (!data || !value) return '-';
|
|
||||||
return addDecimalsFormatNumberQuantum(
|
|
||||||
value,
|
|
||||||
data.asset.decimals,
|
|
||||||
data.asset.quantum
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
colId: 'accounts-actions',
|
|
||||||
field: 'asset.id',
|
|
||||||
...COL_DEFS.actions,
|
|
||||||
minWidth: showDepositButton ? 130 : COL_DEFS.actions.minWidth,
|
|
||||||
maxWidth: showDepositButton ? 130 : COL_DEFS.actions.maxWidth,
|
|
||||||
cellRenderer: ({
|
|
||||||
value: assetId,
|
|
||||||
node,
|
|
||||||
}: VegaICellRendererParams<AccountFields, 'asset.id'>) => {
|
|
||||||
if (!assetId) return null;
|
|
||||||
if (node.rowPinned && node.data?.total === '0') {
|
|
||||||
return (
|
|
||||||
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
variant="primary"
|
|
||||||
data-testid="deposit"
|
|
||||||
onClick={() => {
|
|
||||||
onClickDeposit && onClickDeposit(assetId);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
|
||||||
</Button>
|
|
||||||
</CenteredGridCellWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return isReadOnly ? null : (
|
|
||||||
<AccountsActionsDropdown
|
|
||||||
assetId={assetId}
|
|
||||||
assetContractAddress={
|
|
||||||
node.data?.asset.source?.__typename === 'ERC20'
|
|
||||||
? node.data.asset.source.contractAddress
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
onClickDeposit={() => {
|
|
||||||
onClickDeposit && onClickDeposit(assetId);
|
|
||||||
}}
|
|
||||||
onClickWithdraw={() => {
|
|
||||||
onClickWithdraw && onClickWithdraw(assetId);
|
|
||||||
}}
|
|
||||||
onClickBreakdown={() => {
|
|
||||||
onClickBreakdown && onClickBreakdown(assetId);
|
|
||||||
}}
|
|
||||||
onClickTransfer={() => {
|
|
||||||
onClickTransfer && onClickTransfer(assetId);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return defs;
|
|
||||||
}, [
|
|
||||||
onClickAsset,
|
|
||||||
onClickBreakdown,
|
|
||||||
onClickDeposit,
|
|
||||||
onClickWithdraw,
|
|
||||||
onClickTransfer,
|
|
||||||
isReadOnly,
|
|
||||||
showDepositButton,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);
|
return (
|
||||||
|
<AgGrid
|
||||||
return (
|
{...props}
|
||||||
<AgGrid
|
style={{ width: '100%', height: '100%' }}
|
||||||
{...props}
|
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
||||||
style={{ width: '100%', height: '100%' }}
|
tooltipShowDelay={500}
|
||||||
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
rowData={data}
|
||||||
ref={ref}
|
defaultColDef={{
|
||||||
tooltipShowDelay={500}
|
resizable: true,
|
||||||
rowData={data}
|
tooltipComponent: TooltipCellComponent,
|
||||||
defaultColDef={{
|
sortable: true,
|
||||||
resizable: true,
|
comparator: accountValuesComparator,
|
||||||
tooltipComponent: TooltipCellComponent,
|
}}
|
||||||
sortable: true,
|
columnDefs={colDefs}
|
||||||
comparator: accountValuesComparator,
|
getRowHeight={getPinnedAssetRowHeight}
|
||||||
}}
|
pinnedTopRowData={pinnedRow ? [pinnedRow] : undefined}
|
||||||
columnDefs={colDefs}
|
/>
|
||||||
getRowHeight={getPinnedAssetRowHeight}
|
);
|
||||||
pinnedTopRowData={pinnedRow ? [pinnedRow] : undefined}
|
};
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const useAccountBalance = (assetId?: string) => {
|
|||||||
},
|
},
|
||||||
[assetId]
|
[assetId]
|
||||||
);
|
);
|
||||||
useDataProvider({
|
const { loading, error } = useDataProvider({
|
||||||
dataProvider: accountsDataProvider,
|
dataProvider: accountsDataProvider,
|
||||||
variables,
|
variables,
|
||||||
skip: !pubKey || !assetId,
|
skip: !pubKey || !assetId,
|
||||||
@@ -34,7 +34,9 @@ export const useAccountBalance = (assetId?: string) => {
|
|||||||
() => ({
|
() => ({
|
||||||
accountBalance: pubKey ? accountBalance : '',
|
accountBalance: pubKey ? accountBalance : '',
|
||||||
accountDecimals: pubKey ? accountDecimals : null,
|
accountDecimals: pubKey ? accountDecimals : null,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
}),
|
}),
|
||||||
[accountBalance, accountDecimals, pubKey]
|
[accountBalance, accountDecimals, pubKey, loading, error]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const useMarketAccountBalance = (marketId: string) => {
|
|||||||
},
|
},
|
||||||
[marketId]
|
[marketId]
|
||||||
);
|
);
|
||||||
useDataProvider({
|
const { loading, error } = useDataProvider({
|
||||||
dataProvider: accountsDataProvider,
|
dataProvider: accountsDataProvider,
|
||||||
variables: { partyId: pubKey || '' },
|
variables: { partyId: pubKey || '' },
|
||||||
skip: !pubKey || !marketId,
|
skip: !pubKey || !marketId,
|
||||||
@@ -33,7 +33,9 @@ export const useMarketAccountBalance = (marketId: string) => {
|
|||||||
() => ({
|
() => ({
|
||||||
accountBalance: pubKey ? accountBalance : '',
|
accountBalance: pubKey ? accountBalance : '',
|
||||||
accountDecimals: pubKey ? accountDecimals : null,
|
accountDecimals: pubKey ? accountDecimals : null,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
}),
|
}),
|
||||||
[accountBalance, accountDecimals, pubKey]
|
[accountBalance, accountDecimals, pubKey, loading, error]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,5 +21,4 @@ export * from './lib/type-helpers';
|
|||||||
|
|
||||||
export * from './lib/cells/grid-progress-bar';
|
export * from './lib/cells/grid-progress-bar';
|
||||||
|
|
||||||
export * from './lib/ag-grid-update';
|
|
||||||
export * from './lib/use-datagrid-events';
|
export * from './lib/use-datagrid-events';
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import type { MutableRefObject, RefObject } from 'react';
|
|
||||||
import type { AgGridReact } from 'ag-grid-react';
|
|
||||||
|
|
||||||
type AnyArray = Array<any> | null; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
|
|
||||||
export const isXOrWasEmpty = (prev?: AnyArray, curr?: AnyArray) =>
|
|
||||||
Boolean(Number(!!prev?.length) ^ Number(!!curr?.length));
|
|
||||||
|
|
||||||
export const updateGridData = (
|
|
||||||
dataRef: MutableRefObject<AnyArray>,
|
|
||||||
data: AnyArray,
|
|
||||||
gridRef: RefObject<AgGridReact>
|
|
||||||
) => {
|
|
||||||
const rerender = isXOrWasEmpty(dataRef.current, data);
|
|
||||||
dataRef.current = data;
|
|
||||||
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
|
|
||||||
gridRef.current?.api?.refreshInfiniteCache();
|
|
||||||
}
|
|
||||||
return !rerender;
|
|
||||||
};
|
|
||||||
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
|
|||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
|
||||||
interface MarketNameCellProps {
|
interface MarketNameCellProps {
|
||||||
value?: string;
|
value?: string | null;
|
||||||
data?: { id?: string; marketId?: string; market?: { id: string } };
|
data?: { id?: string; marketId?: string; market?: { id: string } };
|
||||||
idPath?: string;
|
idPath?: string;
|
||||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Control } from 'react-hook-form';
|
import type { Control } from 'react-hook-form';
|
||||||
import type { Market, MarketData } from '@vegaprotocol/markets';
|
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
|
||||||
import { DealTicketMarketAmount } from './deal-ticket-market-amount';
|
import { DealTicketMarketAmount } from './deal-ticket-market-amount';
|
||||||
import { DealTicketLimitAmount } from './deal-ticket-limit-amount';
|
import { DealTicketLimitAmount } from './deal-ticket-limit-amount';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import * as Schema from '@vegaprotocol/types';
|
||||||
@@ -9,7 +9,8 @@ import type { OrderFormFields } from '../../hooks/use-order-form';
|
|||||||
export interface DealTicketAmountProps {
|
export interface DealTicketAmountProps {
|
||||||
control: Control<OrderFormFields>;
|
control: Control<OrderFormFields>;
|
||||||
orderType: Schema.OrderType;
|
orderType: Schema.OrderType;
|
||||||
marketData: MarketData;
|
marketData: StaticMarketData;
|
||||||
|
marketPrice?: string;
|
||||||
market: Market;
|
market: Market;
|
||||||
sizeError?: string;
|
sizeError?: string;
|
||||||
priceError?: string;
|
priceError?: string;
|
||||||
@@ -21,11 +22,18 @@ export interface DealTicketAmountProps {
|
|||||||
export const DealTicketAmount = ({
|
export const DealTicketAmount = ({
|
||||||
orderType,
|
orderType,
|
||||||
marketData,
|
marketData,
|
||||||
|
marketPrice,
|
||||||
...props
|
...props
|
||||||
}: DealTicketAmountProps) => {
|
}: DealTicketAmountProps) => {
|
||||||
switch (orderType) {
|
switch (orderType) {
|
||||||
case Schema.OrderType.TYPE_MARKET:
|
case Schema.OrderType.TYPE_MARKET:
|
||||||
return <DealTicketMarketAmount {...props} marketData={marketData} />;
|
return (
|
||||||
|
<DealTicketMarketAmount
|
||||||
|
{...props}
|
||||||
|
marketData={marketData}
|
||||||
|
marketPrice={marketPrice}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case Schema.OrderType.TYPE_LIMIT:
|
case Schema.OrderType.TYPE_LIMIT:
|
||||||
return <DealTicketLimitAmount {...props} />;
|
return <DealTicketLimitAmount {...props} />;
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
side: Side;
|
side: Side;
|
||||||
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DealTicketButton = ({ side }: Props) => {
|
export const DealTicketButton = ({ side, label }: Props) => {
|
||||||
const buttonClasses = classNames(
|
const buttonClasses = classNames(
|
||||||
'px-10 py-2 uppercase rounded-md text-white w-full',
|
'px-10 py-2 uppercase rounded-md text-white w-full',
|
||||||
{
|
{
|
||||||
@@ -17,7 +18,7 @@ export const DealTicketButton = ({ side }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<button type="submit" data-testid="place-order" className={buttonClasses}>
|
<button type="submit" data-testid="place-order" className={buttonClasses}>
|
||||||
{t('Place order')}
|
{label || t('Place order')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
|
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
||||||
|
import {
|
||||||
|
DealTicketType,
|
||||||
|
useDealTicketTypeStore,
|
||||||
|
} from '../../hooks/use-type-store';
|
||||||
|
import { StopOrder } from './deal-ticket-stop-order';
|
||||||
|
import {
|
||||||
|
useStaticMarketData,
|
||||||
|
useMarket,
|
||||||
|
useMarketPrice,
|
||||||
|
} from '@vegaprotocol/markets';
|
||||||
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { useThrottledDataProvider } from '@vegaprotocol/data-provider';
|
|
||||||
import { useVegaTransactionStore } from '@vegaprotocol/wallet';
|
|
||||||
import { useMarket, marketDataProvider } from '@vegaprotocol/markets';
|
|
||||||
import { DealTicket } from './deal-ticket';
|
import { DealTicket } from './deal-ticket';
|
||||||
|
import { FLAGS } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
export interface DealTicketContainerProps {
|
interface DealTicketContainerProps {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||||
onClickCollateral?: () => void;
|
onClickCollateral?: () => void;
|
||||||
@@ -14,10 +23,9 @@ export interface DealTicketContainerProps {
|
|||||||
|
|
||||||
export const DealTicketContainer = ({
|
export const DealTicketContainer = ({
|
||||||
marketId,
|
marketId,
|
||||||
onMarketClick,
|
...props
|
||||||
onClickCollateral,
|
|
||||||
onDeposit,
|
|
||||||
}: DealTicketContainerProps) => {
|
}: DealTicketContainerProps) => {
|
||||||
|
const type = useDealTicketTypeStore((state) => state.type[marketId]);
|
||||||
const {
|
const {
|
||||||
data: market,
|
data: market,
|
||||||
error: marketError,
|
error: marketError,
|
||||||
@@ -29,15 +37,9 @@ export const DealTicketContainer = ({
|
|||||||
error: marketDataError,
|
error: marketDataError,
|
||||||
loading: marketDataLoading,
|
loading: marketDataLoading,
|
||||||
reload,
|
reload,
|
||||||
} = useThrottledDataProvider(
|
} = useStaticMarketData(marketId);
|
||||||
{
|
const { data: marketPrice } = useMarketPrice(market?.id);
|
||||||
dataProvider: marketDataProvider,
|
|
||||||
variables: { marketId },
|
|
||||||
},
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
const create = useVegaTransactionStore((state) => state.create);
|
const create = useVegaTransactionStore((state) => state.create);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncRenderer
|
<AsyncRenderer
|
||||||
data={market && marketData}
|
data={market && marketData}
|
||||||
@@ -46,14 +48,23 @@ export const DealTicketContainer = ({
|
|||||||
reload={reload}
|
reload={reload}
|
||||||
>
|
>
|
||||||
{market && marketData ? (
|
{market && marketData ? (
|
||||||
<DealTicket
|
FLAGS.STOP_ORDERS &&
|
||||||
market={market}
|
(type === DealTicketType.StopLimit ||
|
||||||
marketData={marketData}
|
type === DealTicketType.StopMarket) ? (
|
||||||
submit={(orderSubmission) => create({ orderSubmission })}
|
<StopOrder
|
||||||
onClickCollateral={onClickCollateral}
|
market={market}
|
||||||
onMarketClick={onMarketClick}
|
marketPrice={marketPrice}
|
||||||
onDeposit={onDeposit}
|
submit={(stopOrdersSubmission) => create({ stopOrdersSubmission })}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<DealTicket
|
||||||
|
{...props}
|
||||||
|
market={market}
|
||||||
|
marketPrice={marketPrice}
|
||||||
|
marketData={marketData}
|
||||||
|
submit={(orderSubmission) => create({ orderSubmission })}
|
||||||
|
/>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<Splash>
|
<Splash>
|
||||||
<p>{t('Could not load market')}</p>
|
<p>{t('Could not load market')}</p>
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import classnames from 'classnames';
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { FeesBreakdown } from '@vegaprotocol/markets';
|
import { FeesBreakdown } from '@vegaprotocol/markets';
|
||||||
|
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
import type { Market } from '@vegaprotocol/markets';
|
import type { Market } from '@vegaprotocol/markets';
|
||||||
import type { EstimatePositionQuery } from '@vegaprotocol/positions';
|
import type { EstimatePositionQuery } from '@vegaprotocol/positions';
|
||||||
import type { EstimateFeesQuery } from '../../hooks/__generated__/EstimateOrder';
|
|
||||||
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
|
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
|
||||||
|
|
||||||
import { formatRange, formatValue } from '@vegaprotocol/utils';
|
import { formatRange, formatValue } from '@vegaprotocol/utils';
|
||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
||||||
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||||
} from '../../constants';
|
} from '../../constants';
|
||||||
|
import { useEstimateFees } from '../../hooks';
|
||||||
|
|
||||||
const emptyValue = '-';
|
const emptyValue = '-';
|
||||||
|
|
||||||
@@ -76,26 +77,82 @@ export const DealTicketFeeDetail = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface DealTicketFeeDetailsProps {
|
export interface DealTicketFeeDetailsProps {
|
||||||
|
assetSymbol: string;
|
||||||
|
order: OrderSubmissionBody['orderSubmission'];
|
||||||
|
market: Market;
|
||||||
|
notionalSize: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DealTicketFeeDetails = ({
|
||||||
|
assetSymbol,
|
||||||
|
order,
|
||||||
|
market,
|
||||||
|
notionalSize,
|
||||||
|
}: DealTicketFeeDetailsProps) => {
|
||||||
|
const feeEstimate = useEstimateFees(order);
|
||||||
|
const { settlementAsset: asset } =
|
||||||
|
market.tradableInstrument.instrument.product;
|
||||||
|
const { decimals: assetDecimals, quantum } = asset;
|
||||||
|
const marketDecimals = market.decimalPlaces;
|
||||||
|
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DealTicketFeeDetail
|
||||||
|
label={t('Notional')}
|
||||||
|
value={formatValue(notionalSize, marketDecimals)}
|
||||||
|
formattedValue={formatValue(notionalSize, marketDecimals)}
|
||||||
|
symbol={quoteName}
|
||||||
|
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||||
|
/>
|
||||||
|
<DealTicketFeeDetail
|
||||||
|
label={t('Fees')}
|
||||||
|
value={
|
||||||
|
feeEstimate?.totalFeeAmount &&
|
||||||
|
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||||
|
}
|
||||||
|
formattedValue={
|
||||||
|
feeEstimate?.totalFeeAmount &&
|
||||||
|
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
||||||
|
}
|
||||||
|
labelDescription={
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
{t(
|
||||||
|
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<FeesBreakdown
|
||||||
|
fees={feeEstimate?.fees}
|
||||||
|
feeFactors={market.fees.factors}
|
||||||
|
symbol={assetSymbol}
|
||||||
|
decimals={assetDecimals}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
symbol={assetSymbol}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface DealTicketMarginDetailsProps {
|
||||||
generalAccountBalance?: string;
|
generalAccountBalance?: string;
|
||||||
marginAccountBalance?: string;
|
marginAccountBalance?: string;
|
||||||
market: Market;
|
market: Market;
|
||||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||||
assetSymbol: string;
|
assetSymbol: string;
|
||||||
notionalSize: string | null;
|
|
||||||
feeEstimate: EstimateFeesQuery['estimateFees'] | undefined;
|
|
||||||
positionEstimate: EstimatePositionQuery['estimatePosition'];
|
positionEstimate: EstimatePositionQuery['estimatePosition'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DealTicketFeeDetails = ({
|
export const DealTicketMarginDetails = ({
|
||||||
marginAccountBalance,
|
marginAccountBalance,
|
||||||
generalAccountBalance,
|
generalAccountBalance,
|
||||||
assetSymbol,
|
assetSymbol,
|
||||||
feeEstimate,
|
|
||||||
market,
|
market,
|
||||||
onMarketClick,
|
onMarketClick,
|
||||||
notionalSize,
|
|
||||||
positionEstimate,
|
positionEstimate,
|
||||||
}: DealTicketFeeDetailsProps) => {
|
}: DealTicketMarginDetailsProps) => {
|
||||||
const [breakdownDialog, setBreakdownDialog] = useState(false);
|
const [breakdownDialog, setBreakdownDialog] = useState(false);
|
||||||
const { pubKey: partyId } = useVegaWallet();
|
const { pubKey: partyId } = useVegaWallet();
|
||||||
const { data: currentMargins } = useDataProvider({
|
const { data: currentMargins } = useDataProvider({
|
||||||
@@ -110,7 +167,6 @@ export const DealTicketFeeDetails = ({
|
|||||||
const { settlementAsset: asset } =
|
const { settlementAsset: asset } =
|
||||||
market.tradableInstrument.instrument.product;
|
market.tradableInstrument.instrument.product;
|
||||||
const { decimals: assetDecimals, quantum } = asset;
|
const { decimals: assetDecimals, quantum } = asset;
|
||||||
const marketDecimals = market.decimalPlaces;
|
|
||||||
let marginRequiredBestCase: string | undefined = undefined;
|
let marginRequiredBestCase: string | undefined = undefined;
|
||||||
let marginRequiredWorstCase: string | undefined = undefined;
|
let marginRequiredWorstCase: string | undefined = undefined;
|
||||||
if (marginEstimate) {
|
if (marginEstimate) {
|
||||||
@@ -251,41 +307,7 @@ export const DealTicketFeeDetails = ({
|
|||||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<DealTicketFeeDetail
|
|
||||||
label={t('Notional')}
|
|
||||||
value={formatValue(notionalSize, marketDecimals)}
|
|
||||||
formattedValue={formatValue(notionalSize, marketDecimals)}
|
|
||||||
symbol={quoteName}
|
|
||||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
|
||||||
/>
|
|
||||||
<DealTicketFeeDetail
|
|
||||||
label={t('Fees')}
|
|
||||||
value={
|
|
||||||
feeEstimate?.totalFeeAmount &&
|
|
||||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
|
||||||
}
|
|
||||||
formattedValue={
|
|
||||||
feeEstimate?.totalFeeAmount &&
|
|
||||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
|
||||||
}
|
|
||||||
labelDescription={
|
|
||||||
<>
|
|
||||||
<span>
|
|
||||||
{t(
|
|
||||||
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<FeesBreakdown
|
|
||||||
fees={feeEstimate?.fees}
|
|
||||||
feeFactors={market.fees.factors}
|
|
||||||
symbol={assetSymbol}
|
|
||||||
decimals={assetDecimals}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
symbol={assetSymbol}
|
|
||||||
/>
|
|
||||||
<DealTicketFeeDetail
|
<DealTicketFeeDetail
|
||||||
label={t('Margin required')}
|
label={t('Margin required')}
|
||||||
value={formatRange(
|
value={formatRange(
|
||||||
@@ -351,6 +373,6 @@ export const DealTicketFeeDetails = ({
|
|||||||
onClose={onAccountBreakdownDialogClose}
|
onClose={onAccountBreakdownDialogClose}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,12 +44,12 @@ export const DealTicketLimitAmount = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t('Size')}
|
label={t('Size')}
|
||||||
labelFor="input-order-size-limit"
|
labelFor="input-order-size-limit"
|
||||||
className="!mb-1"
|
className="!mb-0"
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
name="size"
|
name="size"
|
||||||
@@ -78,16 +78,13 @@ export const DealTicketLimitAmount = ({
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-0 items-center">
|
<div className="pt-7 leading-10">@</div>
|
||||||
<div className="flex"> </div>
|
|
||||||
<div className="flex">@</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<FormGroup
|
<FormGroup
|
||||||
labelFor="input-price-quote"
|
labelFor="input-price-quote"
|
||||||
label={t(`Price (${quoteName})`)}
|
label={t(`Price (${quoteName})`)}
|
||||||
labelAlign="right"
|
labelAlign="right"
|
||||||
className="!mb-1"
|
className="!mb-0"
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
name="price"
|
name="price"
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import {
|
|||||||
} from '@vegaprotocol/utils';
|
} from '@vegaprotocol/utils';
|
||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { Input, InputError, Tooltip } from '@vegaprotocol/ui-toolkit';
|
import { Input, InputError, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||||
import { isMarketInAuction } from '../../utils';
|
import { isMarketInAuction } from '@vegaprotocol/markets';
|
||||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||||
import { getMarketPrice } from '../../utils/get-price';
|
|
||||||
import { Controller } from 'react-hook-form';
|
import { Controller } from 'react-hook-form';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export type DealTicketMarketAmountProps = Omit<
|
export type DealTicketMarketAmountProps = Omit<
|
||||||
DealTicketAmountProps,
|
DealTicketAmountProps,
|
||||||
@@ -19,37 +19,26 @@ export const DealTicketMarketAmount = ({
|
|||||||
control,
|
control,
|
||||||
market,
|
market,
|
||||||
marketData,
|
marketData,
|
||||||
|
marketPrice,
|
||||||
sizeError,
|
sizeError,
|
||||||
update,
|
update,
|
||||||
size,
|
size,
|
||||||
}: DealTicketMarketAmountProps) => {
|
}: DealTicketMarketAmountProps) => {
|
||||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||||
const price = getMarketPrice(marketData);
|
const price = marketPrice;
|
||||||
|
|
||||||
const priceFormatted = price
|
const priceFormatted = price
|
||||||
? addDecimalsFormatNumber(price, market.decimalPlaces)
|
? addDecimalsFormatNumber(price, market.decimalPlaces)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const inAuction = isMarketInAuction(marketData.marketTradingMode);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<div className="flex items-end gap-4 mb-2">
|
<div className="flex items-start gap-4">
|
||||||
<div className="flex-1 text-sm">{t('Size')}</div>
|
|
||||||
<div />
|
|
||||||
<div className="flex-2 text-sm text-right">
|
|
||||||
{isMarketInAuction(marketData.marketTradingMode) && (
|
|
||||||
<Tooltip
|
|
||||||
description={t(
|
|
||||||
'This market is in auction. The uncrossing price is an indication of what the price is expected to be when the auction ends.'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div>{t(`Indicative price`)}</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
<div className="mb-2 text-sm">{t('Size')}</div>
|
||||||
<Controller
|
<Controller
|
||||||
name="size"
|
name="size"
|
||||||
control={control}
|
control={control}
|
||||||
@@ -76,15 +65,29 @@ export const DealTicketMarketAmount = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>@</div>
|
<div className="pt-7 leading-10">@</div>
|
||||||
<div className="flex-1 text-sm text-right" data-testid="last-price">
|
<div className="flex-1 text-sm text-right">
|
||||||
{priceFormatted && quoteName ? (
|
{inAuction && (
|
||||||
<>
|
<Tooltip
|
||||||
~{priceFormatted} {quoteName}
|
description={t(
|
||||||
</>
|
'This market is in auction. The uncrossing price is an indication of what the price is expected to be when the auction ends.'
|
||||||
) : (
|
)}
|
||||||
'-'
|
>
|
||||||
|
<div className="mb-2">{t(`Indicative price`)}</div>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
<div
|
||||||
|
data-testid="last-price"
|
||||||
|
className={classNames('leading-10', { 'pt-7': !inAuction })}
|
||||||
|
>
|
||||||
|
{priceFormatted && quoteName ? (
|
||||||
|
<>
|
||||||
|
~{priceFormatted} {quoteName}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'-'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{sizeError && (
|
{sizeError && (
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { generateMarket } from '../../test-helpers';
|
||||||
|
import { StopOrder } from './deal-ticket-stop-order';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import type { StopOrderFormValues } from '../../hooks/use-stop-order-form-values';
|
||||||
|
import { useStopOrderFormValues } from '../../hooks/use-stop-order-form-values';
|
||||||
|
import type { FeatureFlags } from '@vegaprotocol/environment';
|
||||||
|
|
||||||
|
jest.mock('zustand');
|
||||||
|
jest.mock('./deal-ticket-fee-details', () => ({
|
||||||
|
DealTicketFeeDetails: () => <div data-testid="deal-ticket-fee-details" />,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@vegaprotocol/environment', () => {
|
||||||
|
const actual = jest.requireActual('@vegaprotocol/environment');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
FLAGS: {
|
||||||
|
...actual.FLAGS,
|
||||||
|
STOP_ORDERS: true,
|
||||||
|
} as FeatureFlags,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const marketPrice = '200';
|
||||||
|
const market = generateMarket();
|
||||||
|
const submit = jest.fn();
|
||||||
|
|
||||||
|
function generateJsx(pubKey: string | null = 'pubKey', isReadOnly = false) {
|
||||||
|
return (
|
||||||
|
<MockedProvider>
|
||||||
|
<VegaWalletContext.Provider value={{ pubKey, isReadOnly } as any}>
|
||||||
|
<StopOrder market={market} marketPrice={marketPrice} submit={submit} />
|
||||||
|
</VegaWalletContext.Provider>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitButton = 'place-order';
|
||||||
|
const sizeInput = 'order-size';
|
||||||
|
const priceInput = 'order-price';
|
||||||
|
const triggerPriceInput = 'triggerPrice';
|
||||||
|
const triggerTrailingPercentOffsetInput = 'triggerTrailingPercentOffset';
|
||||||
|
|
||||||
|
const orderTypeTrigger = 'order-type-Stop';
|
||||||
|
const orderTypeLimit = 'order-type-StopLimit';
|
||||||
|
const orderTypeMarket = 'order-type-StopMarket';
|
||||||
|
|
||||||
|
const orderSideBuy = 'order-side-SIDE_BUY';
|
||||||
|
const orderSideSell = 'order-side-SIDE_SELL';
|
||||||
|
|
||||||
|
const triggerDirectionRisesAbove = 'triggerDirection-risesAbove';
|
||||||
|
// const triggerDirectionFallsBelow = 'triggerDirection-fallsBelow';
|
||||||
|
|
||||||
|
const expiryStrategySubmit = 'expiryStrategy-submit';
|
||||||
|
const expiryStrategyCancel = 'expiryStrategy-cancel';
|
||||||
|
|
||||||
|
const triggerTypePrice = 'triggerType-price';
|
||||||
|
const triggerTypeTrailingPercentOffset = 'triggerType-trailingPercentOffset';
|
||||||
|
|
||||||
|
const expire = 'expire';
|
||||||
|
const datePicker = 'date-picker-field';
|
||||||
|
const timeInForce = 'order-tif';
|
||||||
|
|
||||||
|
const sizeErrorMessage = 'stop-order-error-message-size';
|
||||||
|
const priceErrorMessage = 'stop-order-error-message-price';
|
||||||
|
const triggerPriceErrorMessage = 'stop-order-error-message-trigger-price';
|
||||||
|
const triggerTrailingPercentOffsetErrorMessage =
|
||||||
|
'stop-order-error-message-trigger-trailing-percent-offset';
|
||||||
|
|
||||||
|
describe('StopOrder', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display ticket defaults', async () => {
|
||||||
|
render(generateJsx());
|
||||||
|
// place order button should always be enabled
|
||||||
|
expect(screen.getByTestId(submitButton)).toBeEnabled();
|
||||||
|
|
||||||
|
// Assert defaults are used
|
||||||
|
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||||
|
expect(screen.getByTestId(orderTypeLimit).dataset.state).toEqual('checked');
|
||||||
|
await userEvent.click(screen.getByTestId(orderTypeLimit));
|
||||||
|
expect(screen.getByTestId(orderSideBuy).dataset.state).toEqual('checked');
|
||||||
|
expect(screen.getByTestId(sizeInput)).toHaveDisplayValue('0');
|
||||||
|
expect(screen.getByTestId(timeInForce)).toHaveValue(
|
||||||
|
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByTestId(triggerDirectionRisesAbove).dataset.state
|
||||||
|
).toEqual('checked');
|
||||||
|
expect(screen.getByTestId(triggerTypePrice).dataset.state).toEqual(
|
||||||
|
'checked'
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId(expire).dataset.state).toEqual('unchecked');
|
||||||
|
await userEvent.click(screen.getByTestId(expire));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId(expiryStrategySubmit).dataset.state).toEqual(
|
||||||
|
'checked'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display trigger price as price for market type order', async () => {
|
||||||
|
render(generateJsx());
|
||||||
|
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||||
|
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||||
|
await userEvent.type(screen.getByTestId(triggerPriceInput), '10');
|
||||||
|
expect(screen.getByTestId('price')).toHaveTextContent('10.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use local storage state for initial values', async () => {
|
||||||
|
const values: Partial<StopOrderFormValues> = {
|
||||||
|
type: Schema.OrderType.TYPE_LIMIT,
|
||||||
|
side: Schema.Side.SIDE_SELL,
|
||||||
|
size: '0.1',
|
||||||
|
price: '300.22',
|
||||||
|
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||||
|
expire: true,
|
||||||
|
expiryStrategy: Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS,
|
||||||
|
expiresAt: '2023-07-27T16:43:27.000',
|
||||||
|
};
|
||||||
|
|
||||||
|
useStopOrderFormValues.setState({
|
||||||
|
formValues: {
|
||||||
|
[market.id]: values,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
render(generateJsx());
|
||||||
|
// Assert correct defaults are used from store
|
||||||
|
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||||
|
expect(screen.queryByTestId(orderTypeLimit)).toBeChecked();
|
||||||
|
expect(screen.getByTestId(orderSideSell).dataset.state).toEqual('checked');
|
||||||
|
expect(screen.getByTestId(sizeInput)).toHaveDisplayValue(
|
||||||
|
values.size as string
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('order-tif')).toHaveValue(values.timeInForce);
|
||||||
|
expect(screen.getByTestId(priceInput)).toHaveDisplayValue(
|
||||||
|
values.price as string
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId(expire).dataset.state).toEqual('checked');
|
||||||
|
expect(screen.getByTestId(expiryStrategyCancel).dataset.state).toEqual(
|
||||||
|
'checked'
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId(datePicker)).toHaveDisplayValue(
|
||||||
|
values.expiresAt as string
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not submit if no wallet connected', async () => {
|
||||||
|
render(generateJsx(null));
|
||||||
|
await userEvent.type(screen.getByTestId(sizeInput), '1');
|
||||||
|
await userEvent.type(screen.getByTestId(priceInput), '1');
|
||||||
|
await userEvent.type(screen.getByTestId(triggerPriceInput), '1');
|
||||||
|
await userEvent.click(screen.getByTestId(submitButton));
|
||||||
|
expect(submit).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls submit if form is valid', async () => {
|
||||||
|
render(generateJsx());
|
||||||
|
await userEvent.type(screen.getByTestId(sizeInput), '1');
|
||||||
|
await userEvent.type(screen.getByTestId(priceInput), '1');
|
||||||
|
await userEvent.type(screen.getByTestId(triggerPriceInput), '1');
|
||||||
|
await userEvent.click(screen.getByTestId(submitButton));
|
||||||
|
expect(submit).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates size field', async () => {
|
||||||
|
render(generateJsx());
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByTestId(submitButton));
|
||||||
|
|
||||||
|
// default value should be invalid
|
||||||
|
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||||
|
// to small value should be invalid
|
||||||
|
await userEvent.type(screen.getByTestId(sizeInput), '0.01');
|
||||||
|
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// clear and fill using valid value
|
||||||
|
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||||
|
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||||
|
expect(screen.queryByTestId(sizeErrorMessage)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates price field', async () => {
|
||||||
|
render(generateJsx());
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByTestId(submitButton));
|
||||||
|
// price error message should not show if size has error
|
||||||
|
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||||
|
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||||
|
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||||
|
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||||
|
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// switch to market order type error should disappear
|
||||||
|
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||||
|
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||||
|
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||||
|
|
||||||
|
// switch back to limit type
|
||||||
|
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||||
|
await userEvent.click(screen.getByTestId(orderTypeLimit));
|
||||||
|
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// to small value should be invalid
|
||||||
|
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||||
|
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// clear and fill using valid value
|
||||||
|
await userEvent.clear(screen.getByTestId(priceInput));
|
||||||
|
await userEvent.type(screen.getByTestId(priceInput), '0.01');
|
||||||
|
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates trigger price field', async () => {
|
||||||
|
render(generateJsx());
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByTestId(submitButton));
|
||||||
|
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// switch to trailing percentage offset trigger type
|
||||||
|
await userEvent.click(screen.getByTestId(triggerTypeTrailingPercentOffset));
|
||||||
|
expect(screen.queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||||
|
|
||||||
|
// switch back to price trigger type
|
||||||
|
await userEvent.click(screen.getByTestId(triggerTypePrice));
|
||||||
|
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// to small value should be invalid
|
||||||
|
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.001');
|
||||||
|
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// clear and fill using valid value
|
||||||
|
await userEvent.clear(screen.getByTestId(triggerPriceInput));
|
||||||
|
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.01');
|
||||||
|
expect(screen.queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates trigger trailing percentage offset field', async () => {
|
||||||
|
render(generateJsx());
|
||||||
|
|
||||||
|
// should not show error with default form values
|
||||||
|
await userEvent.click(screen.getByTestId(submitButton));
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||||
|
).toBeNull();
|
||||||
|
|
||||||
|
// switch to trailing percentage offset trigger type
|
||||||
|
await userEvent.click(screen.getByTestId(triggerTypeTrailingPercentOffset));
|
||||||
|
expect(
|
||||||
|
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// to small value should be invalid
|
||||||
|
await userEvent.type(
|
||||||
|
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||||
|
'0.09'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// clear and fill using valid value
|
||||||
|
await userEvent.clear(
|
||||||
|
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||||
|
);
|
||||||
|
await userEvent.type(
|
||||||
|
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||||
|
'0.1'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||||
|
).toBeNull();
|
||||||
|
|
||||||
|
// to big value should be invalid
|
||||||
|
await userEvent.clear(
|
||||||
|
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||||
|
);
|
||||||
|
await userEvent.type(
|
||||||
|
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||||
|
'99.91'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
// clear and fill using valid value
|
||||||
|
await userEvent.clear(
|
||||||
|
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||||
|
);
|
||||||
|
await userEvent.type(
|
||||||
|
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||||
|
'99.9'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,588 @@
|
|||||||
|
import type { FormEventHandler } from 'react';
|
||||||
|
import { useRef, useCallback, useEffect } from 'react';
|
||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import type { StopOrdersSubmission } from '@vegaprotocol/wallet';
|
||||||
|
import {
|
||||||
|
formatNumber,
|
||||||
|
removeDecimal,
|
||||||
|
toDecimal,
|
||||||
|
validateAmount,
|
||||||
|
} from '@vegaprotocol/utils';
|
||||||
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
|
import * as Schema from '@vegaprotocol/types';
|
||||||
|
import {
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
Input,
|
||||||
|
Checkbox,
|
||||||
|
FormGroup,
|
||||||
|
InputError,
|
||||||
|
Select,
|
||||||
|
Tooltip,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { getDerivedPrice, type Market } from '@vegaprotocol/markets';
|
||||||
|
import { t } from '@vegaprotocol/i18n';
|
||||||
|
import { ExpirySelector } from './expiry-selector';
|
||||||
|
import { SideSelector } from './side-selector';
|
||||||
|
import { timeInForceLabel, useOrder } from '@vegaprotocol/orders';
|
||||||
|
import {
|
||||||
|
NoWalletWarning,
|
||||||
|
REDUCE_ONLY_TOOLTIP,
|
||||||
|
useNotionalSize,
|
||||||
|
} from './deal-ticket';
|
||||||
|
import { TypeToggle } from './type-selector';
|
||||||
|
import {
|
||||||
|
useStopOrderFormValues,
|
||||||
|
type StopOrderFormValues,
|
||||||
|
} from '../../hooks/use-stop-order-form-values';
|
||||||
|
import {
|
||||||
|
DealTicketType,
|
||||||
|
useDealTicketTypeStore,
|
||||||
|
} from '../../hooks/use-type-store';
|
||||||
|
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-stop-order-submission';
|
||||||
|
import { DealTicketButton } from './deal-ticket-button';
|
||||||
|
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
||||||
|
import { validateExpiration } from '../../utils';
|
||||||
|
|
||||||
|
export interface StopOrderProps {
|
||||||
|
market: Market;
|
||||||
|
marketPrice?: string | null;
|
||||||
|
submit: (order: StopOrdersSubmission) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultValues: Partial<StopOrderFormValues> = {
|
||||||
|
type: Schema.OrderType.TYPE_LIMIT,
|
||||||
|
side: Schema.Side.SIDE_BUY,
|
||||||
|
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||||
|
triggerType: 'price',
|
||||||
|
triggerDirection:
|
||||||
|
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE,
|
||||||
|
expiryStrategy: Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT,
|
||||||
|
size: '0',
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopSubmit: FormEventHandler = (e) => e.preventDefault();
|
||||||
|
|
||||||
|
export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||||
|
const { pubKey, isReadOnly } = useVegaWallet();
|
||||||
|
const setDealTicketType = useDealTicketTypeStore((state) => state.set);
|
||||||
|
const [, updateOrder] = useOrder(market.id);
|
||||||
|
const updateStoredFormValues = useStopOrderFormValues(
|
||||||
|
(state) => state.update
|
||||||
|
);
|
||||||
|
const storedFormValues = useStopOrderFormValues(
|
||||||
|
(state) => state.formValues[market.id]
|
||||||
|
);
|
||||||
|
const { handleSubmit, setValue, watch, control, formState } =
|
||||||
|
useForm<StopOrderFormValues>({
|
||||||
|
defaultValues: { ...defaultValues, ...storedFormValues },
|
||||||
|
});
|
||||||
|
const { errors } = formState;
|
||||||
|
const lastSubmitTime = useRef(0);
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
(data: StopOrderFormValues) => {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
if (lastSubmitTime.current && now - lastSubmitTime.current < 1000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submit(
|
||||||
|
mapFormValuesToStopOrdersSubmission(
|
||||||
|
data,
|
||||||
|
market.id,
|
||||||
|
market.decimalPlaces,
|
||||||
|
market.positionDecimalPlaces
|
||||||
|
)
|
||||||
|
);
|
||||||
|
lastSubmitTime.current = now;
|
||||||
|
},
|
||||||
|
[market.id, market.decimalPlaces, market.positionDecimalPlaces, submit]
|
||||||
|
);
|
||||||
|
const side = watch('side');
|
||||||
|
const expire = watch('expire');
|
||||||
|
const triggerType = watch('triggerType');
|
||||||
|
const triggerPrice = watch('triggerPrice');
|
||||||
|
const timeInForce = watch('timeInForce');
|
||||||
|
const type = watch('type');
|
||||||
|
const rawPrice = watch('price');
|
||||||
|
const rawSize = watch('size');
|
||||||
|
|
||||||
|
if (storedFormValues?.size && rawSize !== storedFormValues?.size) {
|
||||||
|
setValue('size', storedFormValues.size);
|
||||||
|
}
|
||||||
|
if (storedFormValues?.price && rawPrice !== storedFormValues?.price) {
|
||||||
|
setValue('price', storedFormValues.price);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPriceTrigger = triggerType === 'price';
|
||||||
|
const size = removeDecimal(rawSize, market.positionDecimalPlaces);
|
||||||
|
const price =
|
||||||
|
marketPrice &&
|
||||||
|
getDerivedPrice(
|
||||||
|
{
|
||||||
|
type,
|
||||||
|
price: rawPrice && removeDecimal(rawPrice, market.decimalPlaces),
|
||||||
|
},
|
||||||
|
type === Schema.OrderType.TYPE_MARKET && isPriceTrigger && triggerPrice
|
||||||
|
? removeDecimal(triggerPrice, market.decimalPlaces)
|
||||||
|
: marketPrice
|
||||||
|
);
|
||||||
|
|
||||||
|
const notionalSize = useNotionalSize(
|
||||||
|
price,
|
||||||
|
size,
|
||||||
|
market.decimalPlaces,
|
||||||
|
market.positionDecimalPlaces
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = watch((value, { name, type }) => {
|
||||||
|
updateStoredFormValues(market.id, value);
|
||||||
|
});
|
||||||
|
return () => subscription.unsubscribe();
|
||||||
|
}, [watch, market.id, updateStoredFormValues]);
|
||||||
|
|
||||||
|
const { quoteName, settlementAsset: asset } =
|
||||||
|
market.tradableInstrument.instrument.product;
|
||||||
|
|
||||||
|
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||||
|
const priceStep = toDecimal(market?.decimalPlaces);
|
||||||
|
const trailingPercentOffsetStep = '0.1';
|
||||||
|
|
||||||
|
const priceFormatted =
|
||||||
|
isPriceTrigger && triggerPrice
|
||||||
|
? formatNumber(triggerPrice, market.decimalPlaces)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={isReadOnly || !pubKey ? stopSubmit : handleSubmit(onSubmit)}
|
||||||
|
noValidate
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="type"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
const { value } = field;
|
||||||
|
return (
|
||||||
|
<TypeToggle
|
||||||
|
value={
|
||||||
|
value === Schema.OrderType.TYPE_LIMIT
|
||||||
|
? DealTicketType.StopLimit
|
||||||
|
: DealTicketType.StopMarket
|
||||||
|
}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const type = value as DealTicketType;
|
||||||
|
setDealTicketType(market.id, type);
|
||||||
|
if (
|
||||||
|
type === DealTicketType.Limit ||
|
||||||
|
type === DealTicketType.Market
|
||||||
|
) {
|
||||||
|
updateOrder({
|
||||||
|
type:
|
||||||
|
type === DealTicketType.Limit
|
||||||
|
? Schema.OrderType.TYPE_LIMIT
|
||||||
|
: Schema.OrderType.TYPE_MARKET,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setValue(
|
||||||
|
'type',
|
||||||
|
type === DealTicketType.StopLimit
|
||||||
|
? Schema.OrderType.TYPE_LIMIT
|
||||||
|
: Schema.OrderType.TYPE_MARKET
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.type && (
|
||||||
|
<InputError testId="stop-order-error-message-type">
|
||||||
|
{errors.type.message}
|
||||||
|
</InputError>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="side"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<SideSelector value={field.value} onValueChange={field.onChange} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormGroup label={t('Trigger')} compact={true} labelFor="">
|
||||||
|
<Controller
|
||||||
|
name="triggerDirection"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
const { onChange, value } = field;
|
||||||
|
return (
|
||||||
|
<RadioGroup
|
||||||
|
name="triggerDirection"
|
||||||
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
orientation="horizontal"
|
||||||
|
className="mb-2"
|
||||||
|
>
|
||||||
|
<Radio
|
||||||
|
value={
|
||||||
|
Schema.StopOrderTriggerDirection
|
||||||
|
.TRIGGER_DIRECTION_RISES_ABOVE
|
||||||
|
}
|
||||||
|
id="triggerDirection-risesAbove"
|
||||||
|
label={'Rises above'}
|
||||||
|
/>
|
||||||
|
<Radio
|
||||||
|
value={
|
||||||
|
Schema.StopOrderTriggerDirection
|
||||||
|
.TRIGGER_DIRECTION_FALLS_BELOW
|
||||||
|
}
|
||||||
|
id="triggerDirection-fallsBelow"
|
||||||
|
label={'Falls below'}
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{isPriceTrigger && (
|
||||||
|
<div className="mb-2">
|
||||||
|
<Controller
|
||||||
|
name="triggerPrice"
|
||||||
|
rules={{
|
||||||
|
required: t('You need provide a price'),
|
||||||
|
min: {
|
||||||
|
value: priceStep,
|
||||||
|
message: t('Price cannot be lower than ' + priceStep),
|
||||||
|
},
|
||||||
|
validate: validateAmount(priceStep, 'Price'),
|
||||||
|
}}
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
const { value, ...props } = field;
|
||||||
|
return (
|
||||||
|
<div className="mb-2">
|
||||||
|
<Input
|
||||||
|
data-testid="triggerPrice"
|
||||||
|
type="number"
|
||||||
|
step={priceStep}
|
||||||
|
appendElement={asset.symbol}
|
||||||
|
value={value || ''}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.triggerPrice && (
|
||||||
|
<InputError testId="stop-order-error-message-trigger-price">
|
||||||
|
{errors.triggerPrice.message}
|
||||||
|
</InputError>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isPriceTrigger && (
|
||||||
|
<div className="mb-2">
|
||||||
|
<Controller
|
||||||
|
name="triggerTrailingPercentOffset"
|
||||||
|
control={control}
|
||||||
|
rules={{
|
||||||
|
required: t('You need provide a trailing percent offset'),
|
||||||
|
min: {
|
||||||
|
value: trailingPercentOffsetStep,
|
||||||
|
message: t(
|
||||||
|
'Trailing percent offset cannot be lower than ' +
|
||||||
|
trailingPercentOffsetStep
|
||||||
|
),
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
value: '99.9',
|
||||||
|
message: t(
|
||||||
|
'Trailing percent offset cannot be higher than 99.9'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
validate: validateAmount(
|
||||||
|
trailingPercentOffsetStep,
|
||||||
|
'Trailing percentage offset'
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
render={({ field }) => {
|
||||||
|
const { value, ...props } = field;
|
||||||
|
return (
|
||||||
|
<div className="mb-2">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
step={trailingPercentOffsetStep}
|
||||||
|
appendElement="%"
|
||||||
|
data-testid="triggerTrailingPercentOffset"
|
||||||
|
value={value || ''}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{errors.triggerTrailingPercentOffset && (
|
||||||
|
<InputError testId="stop-order-error-message-trigger-trailing-percent-offset">
|
||||||
|
{errors.triggerTrailingPercentOffset.message}
|
||||||
|
</InputError>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Controller
|
||||||
|
name="triggerType"
|
||||||
|
control={control}
|
||||||
|
rules={{ deps: ['triggerTrailingPercentOffset', 'triggerPrice'] }}
|
||||||
|
render={({ field }) => {
|
||||||
|
const { onChange, value } = field;
|
||||||
|
return (
|
||||||
|
<RadioGroup
|
||||||
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
orientation="horizontal"
|
||||||
|
>
|
||||||
|
<Radio value="price" id="triggerType-price" label={'Price'} />
|
||||||
|
<Radio
|
||||||
|
value="trailingPercentOffset"
|
||||||
|
id="triggerType-trailingPercentOffset"
|
||||||
|
label={'Trailing Percent Offset'}
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<div className="mb-2">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<FormGroup
|
||||||
|
labelFor="input-price-quote"
|
||||||
|
label={t(`Size`)}
|
||||||
|
className="!mb-0 flex-1"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="size"
|
||||||
|
control={control}
|
||||||
|
rules={{
|
||||||
|
required: t('You need to provide a size'),
|
||||||
|
min: {
|
||||||
|
value: sizeStep,
|
||||||
|
message: t('Size cannot be lower than ' + sizeStep),
|
||||||
|
},
|
||||||
|
validate: validateAmount(sizeStep, 'Size'),
|
||||||
|
}}
|
||||||
|
render={({ field }) => {
|
||||||
|
const { value, ...props } = field;
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
id="order-size"
|
||||||
|
className="w-full"
|
||||||
|
type="number"
|
||||||
|
step={sizeStep}
|
||||||
|
min={sizeStep}
|
||||||
|
onWheel={(e) => e.currentTarget.blur()}
|
||||||
|
data-testid="order-size"
|
||||||
|
value={value || ''}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<div className="pt-7 leading-10">@</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
{type === Schema.OrderType.TYPE_LIMIT ? (
|
||||||
|
<FormGroup
|
||||||
|
labelFor="input-price-quote"
|
||||||
|
label={t(`Price (${quoteName})`)}
|
||||||
|
labelAlign="right"
|
||||||
|
className="!mb-0"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="price"
|
||||||
|
control={control}
|
||||||
|
rules={{
|
||||||
|
deps: 'type',
|
||||||
|
required: t('You need provide a price'),
|
||||||
|
min: {
|
||||||
|
value: priceStep,
|
||||||
|
message: t('Price cannot be lower than ' + priceStep),
|
||||||
|
},
|
||||||
|
validate: validateAmount(priceStep, 'Price'),
|
||||||
|
}}
|
||||||
|
render={({ field }) => {
|
||||||
|
const { value, ...props } = field;
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
id="input-price-quote"
|
||||||
|
className="w-full"
|
||||||
|
type="number"
|
||||||
|
step={priceStep}
|
||||||
|
data-testid="order-price"
|
||||||
|
onWheel={(e) => e.currentTarget.blur()}
|
||||||
|
value={value || ''}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="text-sm text-right pt-7 leading-10"
|
||||||
|
data-testid="price"
|
||||||
|
>
|
||||||
|
{priceFormatted && quoteName
|
||||||
|
? `~${priceFormatted} ${quoteName}`
|
||||||
|
: '-'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{errors.size && (
|
||||||
|
<InputError testId="stop-order-error-message-size">
|
||||||
|
{errors.size.message}
|
||||||
|
</InputError>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!errors.size &&
|
||||||
|
errors.price &&
|
||||||
|
type === Schema.OrderType.TYPE_LIMIT && (
|
||||||
|
<InputError testId="stop-order-error-message-price">
|
||||||
|
{errors.price.message}
|
||||||
|
</InputError>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<FormGroup
|
||||||
|
label={t('Time in force')}
|
||||||
|
labelFor="select-time-in-force"
|
||||||
|
compact={true}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="timeInForce"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
id="select-time-in-force"
|
||||||
|
className="w-full"
|
||||||
|
data-testid="order-tif"
|
||||||
|
{...field}
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
key={Schema.OrderTimeInForce.TIME_IN_FORCE_IOC}
|
||||||
|
value={Schema.OrderTimeInForce.TIME_IN_FORCE_IOC}
|
||||||
|
>
|
||||||
|
{timeInForceLabel(Schema.OrderTimeInForce.TIME_IN_FORCE_IOC)}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
key={Schema.OrderTimeInForce.TIME_IN_FORCE_FOK}
|
||||||
|
value={Schema.OrderTimeInForce.TIME_IN_FORCE_FOK}
|
||||||
|
>
|
||||||
|
{timeInForceLabel(Schema.OrderTimeInForce.TIME_IN_FORCE_FOK)}
|
||||||
|
</option>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
{errors.timeInForce && (
|
||||||
|
<InputError testId="stop-error-message-tif">
|
||||||
|
{errors.timeInForce.message}
|
||||||
|
</InputError>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 pb-2 justify-between">
|
||||||
|
<Controller
|
||||||
|
name="expire"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
const { onChange: onCheckedChange, value } = field;
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
onCheckedChange={onCheckedChange}
|
||||||
|
checked={value}
|
||||||
|
name="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>
|
||||||
|
{expire && (
|
||||||
|
<>
|
||||||
|
<FormGroup
|
||||||
|
label={t('Strategy')}
|
||||||
|
labelFor="expiryStrategy"
|
||||||
|
compact={true}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="expiryStrategy"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<RadioGroup orientation="horizontal" {...field}>
|
||||||
|
<Radio
|
||||||
|
value={
|
||||||
|
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||||
|
}
|
||||||
|
id="expiryStrategy-submit"
|
||||||
|
label={'Submit'}
|
||||||
|
/>
|
||||||
|
<Radio
|
||||||
|
value={
|
||||||
|
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS
|
||||||
|
}
|
||||||
|
id="expiryStrategy-cancel"
|
||||||
|
label={'Cancel'}
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<div className="mb-2">
|
||||||
|
<Controller
|
||||||
|
name="expiresAt"
|
||||||
|
control={control}
|
||||||
|
rules={{
|
||||||
|
validate: validateExpiration,
|
||||||
|
}}
|
||||||
|
render={({ field }) => {
|
||||||
|
const { value, onChange: onSelect } = field;
|
||||||
|
return (
|
||||||
|
<ExpirySelector
|
||||||
|
value={value}
|
||||||
|
onSelect={onSelect}
|
||||||
|
errorMessage={errors.expiresAt?.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<NoWalletWarning isReadOnly={isReadOnly} />
|
||||||
|
<DealTicketButton side={side} label={t('Submit Stop Order')} />
|
||||||
|
<DealTicketFeeDetails
|
||||||
|
order={{
|
||||||
|
marketId: market.id,
|
||||||
|
price: price || undefined,
|
||||||
|
side,
|
||||||
|
size,
|
||||||
|
timeInForce,
|
||||||
|
type,
|
||||||
|
}}
|
||||||
|
notionalSize={notionalSize}
|
||||||
|
assetSymbol={asset.symbol}
|
||||||
|
market={market}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user