Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e50c42182 |
@@ -125,7 +125,7 @@ jobs:
|
||||
#----------------------------------------------
|
||||
- name: Run tests
|
||||
working-directory: ./console-test
|
||||
run: poetry run pytest -v -s --numprocesses auto --dist loadfile --durations=20
|
||||
run: poetry run pytest -s --numprocesses auto --dist loadfile
|
||||
- name: Check files
|
||||
run: |
|
||||
ls -al .
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"proposalSubmission": {
|
||||
"rationale": {
|
||||
"title": "Test new asset proposal",
|
||||
"description": "E2E test for proposals"
|
||||
},
|
||||
"terms": {
|
||||
"newAsset": {
|
||||
"changes": {
|
||||
"name": "USDT Coin",
|
||||
"symbol": "USDT",
|
||||
"decimals": "18",
|
||||
"quantum": "1",
|
||||
"erc20": {
|
||||
"contractAddress": "0xb404c51bbc10dcbe948077f18a4b8e553d160084",
|
||||
"withdrawThreshold": "10",
|
||||
"lifetimeLimit": "10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"closingTimestamp": 1724339572,
|
||||
"enactmentTimestamp": 1724339572,
|
||||
"validationTimestamp": 1692799617
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import { getNewAssetTxBody } from '../support/governance.functions';
|
||||
|
||||
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('/');
|
||||
@@ -12,8 +11,6 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
|
||||
it('Able to view proposal', function () {
|
||||
const proposalTitle = 'Add Lorem Ipsum market';
|
||||
|
||||
cy.navigate_to('governanceProposals');
|
||||
cy.getByTestId(proposalHeading).should('be.visible');
|
||||
cy.contains(proposalTitle)
|
||||
@@ -25,9 +22,6 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
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.getByTestId('vote-progress-bar-for')
|
||||
.invoke('attr', 'style')
|
||||
.should('eq', 'width: 100%;');
|
||||
cy.get('[col-id="cDate"]')
|
||||
.invoke('text')
|
||||
.should('match', dateTimeRegex);
|
||||
@@ -41,12 +35,9 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
cy.getByTestId('dialog-title').should('have.text', proposalTitle);
|
||||
cy.get('.language-json').should('exist');
|
||||
cy.getByTestId('icon-cross').click();
|
||||
});
|
||||
|
||||
it.skip('Proposal page displayed on mobile', function () {
|
||||
const proposalTitle = 'Add Lorem Ipsum market';
|
||||
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.navigate_to('governanceProposals', true);
|
||||
cy.getByTestId(proposalHeading).should('be.visible');
|
||||
@@ -54,40 +45,5 @@ context('Proposal page', { tags: '@smoke' }, function () {
|
||||
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to view new asset proposal', function () {
|
||||
const proposalTitle = 'Test new asset proposal';
|
||||
const newAssetProposalBody = getNewAssetTxBody();
|
||||
cy.VegaWalletSubmitProposal(newAssetProposalBody);
|
||||
|
||||
cy.visit('/');
|
||||
cy.navigate_to('governanceProposals');
|
||||
cy.contains(proposalTitle)
|
||||
.parent()
|
||||
.parent()
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
|
||||
cy.get_element_by_col_id('type').should('have.text', 'NewAsset');
|
||||
cy.get_element_by_col_id('state').should(
|
||||
'have.text',
|
||||
'Waiting for Node Vote'
|
||||
);
|
||||
cy.getByTestId('vote-progress').should('be.visible');
|
||||
cy.getByTestId('vote-progress-bar-against')
|
||||
.invoke('attr', 'style')
|
||||
.should('eq', 'width: 100%;');
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import { addSeconds, millisecondsToSeconds } from 'date-fns';
|
||||
|
||||
export function createSuccessorMarketProposal(parentMarketId) {
|
||||
cy.VegaWalletSubmitProposal(getSuccessorTxBody(parentMarketId));
|
||||
}
|
||||
|
||||
function getSuccessorTxBody(parentMarketId) {
|
||||
const MIN_CLOSE_SEC = 500;
|
||||
const MIN_ENACT_SEC = 700;
|
||||
|
||||
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
|
||||
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
|
||||
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
|
||||
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
|
||||
|
||||
return {
|
||||
proposalSubmission: {
|
||||
rationale: {
|
||||
@@ -132,49 +122,8 @@ function getSuccessorTxBody(parentMarketId) {
|
||||
},
|
||||
},
|
||||
},
|
||||
closingTimestamp,
|
||||
enactmentTimestamp,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getNewAssetTxBody() {
|
||||
const MIN_CLOSE_SEC = 500;
|
||||
const MIN_ENACT_SEC = 700;
|
||||
const MIN_VALID_SEC = 60;
|
||||
|
||||
const closingDate = addSeconds(new Date(), MIN_CLOSE_SEC);
|
||||
const enactmentDate = addSeconds(closingDate, MIN_ENACT_SEC);
|
||||
const validationDate = addSeconds(new Date(), MIN_VALID_SEC);
|
||||
|
||||
const closingTimestamp = millisecondsToSeconds(closingDate.getTime());
|
||||
const enactmentTimestamp = millisecondsToSeconds(enactmentDate.getTime());
|
||||
const validationTimestamp = millisecondsToSeconds(validationDate.getTime());
|
||||
|
||||
return {
|
||||
proposalSubmission: {
|
||||
rationale: {
|
||||
title: 'Test new asset proposal',
|
||||
description: 'E2E test for proposals',
|
||||
},
|
||||
terms: {
|
||||
newAsset: {
|
||||
changes: {
|
||||
name: 'USDT Coin',
|
||||
symbol: 'USDT',
|
||||
decimals: '18',
|
||||
quantum: '1',
|
||||
erc20: {
|
||||
contractAddress: '0xb404c51bbc10dcbe948077f18a4b8e553d160084',
|
||||
withdrawThreshold: '10',
|
||||
lifetimeLimit: '10',
|
||||
},
|
||||
},
|
||||
},
|
||||
closingTimestamp,
|
||||
enactmentTimestamp,
|
||||
validationTimestamp,
|
||||
closingTimestamp: 1695666618,
|
||||
enactmentTimestamp: 1695666618,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,6 +22,8 @@ NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -19,9 +19,6 @@ NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
||||
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
|
||||
@@ -29,4 +26,4 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
@@ -14,11 +14,8 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_REST_URL=https://api.n00.devnet1.vega.xyz/api/v2/
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
@@ -14,11 +14,9 @@ NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_REST_URL=https://api.vega.community/api/v2/
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
@@ -13,11 +13,9 @@ NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-mirror-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_REST_URL=https://api.mainnet-mirror.vega.rocks/api/v2/
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
@@ -10,11 +10,8 @@ NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
@@ -15,11 +15,8 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
@@ -12,11 +12,8 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
+38
-21
@@ -1,27 +1,44 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { RoundedWrapper, ShowMore } from '@vegaprotocol/ui-toolkit';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RoundedWrapper } from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
|
||||
export const ProposalDescription = ({
|
||||
description,
|
||||
}: {
|
||||
description: string;
|
||||
}) => (
|
||||
<section data-testid="proposal-description">
|
||||
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
|
||||
<div className="p-2">
|
||||
<ShowMore>
|
||||
<ReactMarkdown
|
||||
className="react-markdown-container"
|
||||
/* Prevents HTML embedded in the description from rendering */
|
||||
skipHtml={true}
|
||||
/* Stops users embedding images which could be used for tracking */
|
||||
disallowedElements={['img']}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{description}
|
||||
</ReactMarkdown>
|
||||
</ShowMore>
|
||||
</div>
|
||||
</RoundedWrapper>
|
||||
</section>
|
||||
);
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDescription, setShowDescription] = useState(false);
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-description">
|
||||
<CollapsibleToggle
|
||||
toggleState={showDescription}
|
||||
setToggleState={setShowDescription}
|
||||
dataTestId={'proposal-description-toggle'}
|
||||
>
|
||||
<SubHeading title={t('proposalDescription')} />
|
||||
</CollapsibleToggle>
|
||||
|
||||
{showDescription && (
|
||||
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
|
||||
<div className="p-2">
|
||||
<ReactMarkdown
|
||||
className="react-markdown-container"
|
||||
/* Prevents HTML embedded in the description from rendering */
|
||||
skipHtml={true}
|
||||
/* Stops users embedding images which could be used for tracking */
|
||||
disallowedElements={['img']}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{description}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</RoundedWrapper>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
+152
-138
@@ -15,6 +15,8 @@ import {
|
||||
SettlementAssetInfoPanel,
|
||||
} from '@vegaprotocol/markets';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
Button,
|
||||
CopyWithTooltip,
|
||||
Dialog,
|
||||
@@ -41,9 +43,6 @@ export const useMarketDataDialogStore = create<MarketDataDialogState>(
|
||||
})
|
||||
);
|
||||
|
||||
const marketDataHeaderStyles =
|
||||
'font-alpha calt text-base border-b border-vega-dark-200 mt-2 py-2';
|
||||
|
||||
export const ProposalMarketData = ({
|
||||
marketData,
|
||||
parentMarketData,
|
||||
@@ -77,14 +76,6 @@ export const ProposalMarketData = ({
|
||||
parentTerminationData !== undefined &&
|
||||
isEqual(terminationData, parentTerminationData);
|
||||
|
||||
const showParentPriceMonitoringBounds =
|
||||
parentMarketData?.priceMonitoringSettings?.parameters?.triggers !==
|
||||
undefined &&
|
||||
!isEqual(
|
||||
marketData.priceMonitoringSettings?.parameters?.triggers,
|
||||
parentMarketData?.priceMonitoringSettings?.parameters?.triggers
|
||||
);
|
||||
|
||||
const getSigners = (data: DataSourceDefinition) => {
|
||||
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
|
||||
const signers = data.sourceType.sourceType.signers || [];
|
||||
@@ -117,141 +108,164 @@ export const ProposalMarketData = ({
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mb-10">
|
||||
<h2 className={marketDataHeaderStyles}>{t('Key details')}</h2>
|
||||
<KeyDetailsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Instrument')}</h2>
|
||||
<InstrumentInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
{isEqual(
|
||||
getSigners(settlementData),
|
||||
getSigners(terminationData)
|
||||
) ? (
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>{t('Oracle')}</h2>
|
||||
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
parentMarket={
|
||||
isParentSettlementDataEqual ? undefined : parentMarketData
|
||||
<Accordion>
|
||||
<AccordionItem
|
||||
itemId="key-details"
|
||||
title={t('Key details')}
|
||||
content={
|
||||
<KeyDetailsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="instrument"
|
||||
title={t('Instrument')}
|
||||
content={
|
||||
<InstrumentInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{isEqual(
|
||||
getSigners(settlementData),
|
||||
getSigners(terminationData)
|
||||
) ? (
|
||||
<AccordionItem
|
||||
itemId="oracles"
|
||||
title={t('Oracle')}
|
||||
content={
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
parentMarket={
|
||||
isParentSettlementDataEqual
|
||||
? undefined
|
||||
: parentMarketData
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Settlement Oracle')}
|
||||
</h2>
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
parentMarket={
|
||||
isParentSettlementDataEqual ? undefined : parentMarketData
|
||||
}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Termination Oracle')}
|
||||
</h2>
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="termination"
|
||||
parentMarket={
|
||||
isParentTerminationDataEqual ? undefined : parentMarketData
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/*Note: successor markets will not differ in their settlement*/}
|
||||
{/*assets, so no need to pass in parent market data for comparison.*/}
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Settlement assets')}</h2>
|
||||
<SettlementAssetInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Metadata')}</h2>
|
||||
<MetadataInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk model')}</h2>
|
||||
<RiskModelInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk parameters')}</h2>
|
||||
<RiskParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>{t('Risk factors')}</h2>
|
||||
<RiskFactorsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
{showParentPriceMonitoringBounds &&
|
||||
(
|
||||
parentMarketData?.priceMonitoringSettings?.parameters
|
||||
?.triggers || []
|
||||
).map((_, triggerIndex) => (
|
||||
) : (
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t(`Parent price monitoring bounds ${triggerIndex + 1}`)}
|
||||
</h2>
|
||||
<AccordionItem
|
||||
itemId="settlement-oracle"
|
||||
title={t('Settlement Oracle')}
|
||||
content={
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="settlementData"
|
||||
parentMarket={
|
||||
isParentSettlementDataEqual
|
||||
? undefined
|
||||
: parentMarketData
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="text-vega-dark-300 line-through">
|
||||
<AccordionItem
|
||||
itemId="termination-oracle"
|
||||
title={t('Termination Oracle')}
|
||||
content={
|
||||
<OracleInfoPanel
|
||||
market={marketData}
|
||||
type="termination"
|
||||
parentMarket={
|
||||
isParentTerminationDataEqual
|
||||
? undefined
|
||||
: parentMarketData
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{/*Note: successor markets will not differ in their settlement*/}
|
||||
{/*assets, so no need to pass in parent market data for comparison.*/}
|
||||
<AccordionItem
|
||||
itemId="settlement-asset"
|
||||
title={t('Settlement asset')}
|
||||
content={<SettlementAssetInfoPanel market={marketData} />}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="metadata"
|
||||
title={t('Metadata')}
|
||||
content={
|
||||
<MetadataInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-model"
|
||||
title={t('Risk model')}
|
||||
content={
|
||||
<RiskModelInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-parameters"
|
||||
title={t('Risk parameters')}
|
||||
content={
|
||||
<RiskParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="risk-factors"
|
||||
title={t('Risk factors')}
|
||||
content={
|
||||
<RiskFactorsInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{(
|
||||
marketData.priceMonitoringSettings?.parameters?.triggers || []
|
||||
).map((_, triggerIndex) => (
|
||||
<AccordionItem
|
||||
itemId={`trigger-${triggerIndex}`}
|
||||
title={t(`Price monitoring bounds ${triggerIndex + 1}`)}
|
||||
content={
|
||||
<PriceMonitoringBoundsInfoPanel
|
||||
market={parentMarketData}
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
triggerIndex={triggerIndex}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
|
||||
{(
|
||||
marketData.priceMonitoringSettings?.parameters?.triggers || []
|
||||
).map((_, triggerIndex) => (
|
||||
<>
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t(`Price monitoring bounds ${triggerIndex + 1}`)}
|
||||
</h2>
|
||||
|
||||
<PriceMonitoringBoundsInfoPanel
|
||||
market={marketData}
|
||||
triggerIndex={triggerIndex}
|
||||
}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Liquidity monitoring parameters')}
|
||||
</h2>
|
||||
<LiquidityMonitoringParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
|
||||
<h2 className={marketDataHeaderStyles}>
|
||||
{t('Liquidity price range')}
|
||||
</h2>
|
||||
<LiquidityPriceRangeInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
))}
|
||||
<AccordionItem
|
||||
itemId="liqudity-monitoring-parameters"
|
||||
title={t('Liquidity monitoring parameters')}
|
||||
content={
|
||||
<LiquidityMonitoringParametersInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AccordionItem
|
||||
itemId="liquidity-price-range"
|
||||
title={t('Liquidity price range')}
|
||||
content={
|
||||
<LiquidityPriceRangeInfoPanel
|
||||
market={marketData}
|
||||
parentMarket={parentMarketData}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Accordion>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -36,4 +36,4 @@ CYPRESS_VEGA_WALLET_API_TOKEN=
|
||||
|
||||
# Cosmic elevator flags (MUST be doubled with CYPRESS_ prefix)
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
CYPRESS_NX_SUCCESSOR_MARKETS=true
|
||||
CYPRESS_NX_SUCCESSOR_MARKETS=true
|
||||
@@ -0,0 +1,239 @@
|
||||
import { aliasGQLQuery, checkSorting } from '@vegaprotocol/cypress';
|
||||
import type { ProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
|
||||
const rowSelector =
|
||||
'[data-testid="tab-proposed-markets"] .ag-center-cols-container .ag-row';
|
||||
const colMarketId = '[col-id="market"] [data-testid="market-code"]';
|
||||
|
||||
describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
|
||||
it('can see table headers', () => {
|
||||
const headers = [
|
||||
'Market',
|
||||
'Description',
|
||||
'Settlement asset',
|
||||
'State',
|
||||
'Parent market',
|
||||
'Voting',
|
||||
'Closing date',
|
||||
'Enactment date',
|
||||
'',
|
||||
];
|
||||
cy.getByTestId('tab-proposed-markets').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
.get('.ag-header-cell-text')
|
||||
.each(($header, i) => {
|
||||
cy.wrap($header).should('have.text', headers[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders markets correctly', () => {
|
||||
// 6001-MARK-049
|
||||
cy.get(rowSelector).first().find(colMarketId).should('have.text', 'ETHUSD');
|
||||
|
||||
// 6001-MARK-050
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="description"]')
|
||||
.should('have.text', 'ETHUSD');
|
||||
|
||||
// 6001-MARK-074
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[title="Future"]')
|
||||
.should('have.text', 'Futr');
|
||||
|
||||
// 6001-MARK-051
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="asset"]')
|
||||
.should('have.text', 'tDAI TEST');
|
||||
|
||||
// 6001-MARK-052
|
||||
// 6001-MARK-053
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="state"]')
|
||||
.should('have.text', 'Open');
|
||||
|
||||
// 6001-MARK-054
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="voting"]')
|
||||
.should('have.text', '');
|
||||
|
||||
// 6001-MARK-056
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="closing-date"]')
|
||||
.should('not.be.empty');
|
||||
|
||||
// 6001-MARK-057
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="enactment-date"]')
|
||||
.should('not.be.empty');
|
||||
});
|
||||
|
||||
it('can open row actions', () => {
|
||||
// 6001-MARK-058
|
||||
cy.get('.ag-pinned-right-cols-container')
|
||||
.find('[col-id="proposal-actions"]')
|
||||
.first()
|
||||
.find('button')
|
||||
.click();
|
||||
|
||||
const dropdownContent = '[data-testid="proposal-actions-content"]';
|
||||
const dropdownContentItem = '[role="menuitem"]';
|
||||
|
||||
// 6001-MARK-059
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(0)
|
||||
.find('a')
|
||||
.should('have.text', 'View proposal')
|
||||
.and(
|
||||
'have.attr',
|
||||
'href',
|
||||
`${Cypress.env(
|
||||
'VEGA_TOKEN_URL'
|
||||
)}/proposals/e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829`
|
||||
);
|
||||
});
|
||||
|
||||
// 6001-MARK-060
|
||||
it('can see proposed market link', () => {
|
||||
cy.getByTestId('tab-proposed-markets')
|
||||
.find('[data-testid="external-link"]')
|
||||
.should('have.length', 11)
|
||||
.last()
|
||||
.should('have.text', 'Propose a new market')
|
||||
.and(
|
||||
'have.attr',
|
||||
'href',
|
||||
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
|
||||
);
|
||||
});
|
||||
it('proposed markets tab should be sorted properly', () => {
|
||||
// 6001-MARK-062
|
||||
cy.get('[data-testid="Proposed markets"]').click({ force: true });
|
||||
const marketColDefault = [
|
||||
'ETHUSD',
|
||||
'LINKUSD',
|
||||
'ETHUSD',
|
||||
'ETHDAI.MF21',
|
||||
'AAPL.MF21',
|
||||
'BTCUSD.MF21',
|
||||
'TSLA.QM21',
|
||||
'AAVEDAI.MF21',
|
||||
'ETHBTC.QM21',
|
||||
'UNIDAI.MF21',
|
||||
];
|
||||
const marketColAsc = [
|
||||
'AAPL.MF21',
|
||||
'AAVEDAI.MF21',
|
||||
'BTCUSD.MF21',
|
||||
'ETHBTC.QM21',
|
||||
'ETHDAI.MF21',
|
||||
'ETHUSD',
|
||||
'ETHUSD',
|
||||
'LINKUSD',
|
||||
'TSLA.QM21',
|
||||
'UNIDAI.MF21',
|
||||
];
|
||||
const marketColDesc = [
|
||||
'UNIDAI.MF21',
|
||||
'TSLA.QM21',
|
||||
'LINKUSD',
|
||||
'ETHUSD',
|
||||
'ETHUSD',
|
||||
'ETHDAI.MF21',
|
||||
'ETHBTC.QM21',
|
||||
'BTCUSD.MF21',
|
||||
'AAVEDAI.MF21',
|
||||
'AAPL.MF21',
|
||||
];
|
||||
checkSorting(
|
||||
'market',
|
||||
marketColDefault,
|
||||
marketColAsc,
|
||||
marketColDesc,
|
||||
' [data-testid="market-code"]'
|
||||
);
|
||||
|
||||
const stateColDefault = [
|
||||
'Open',
|
||||
'Passed',
|
||||
'Waiting for Node Vote',
|
||||
'Open',
|
||||
'Passed',
|
||||
'Open',
|
||||
'Passed',
|
||||
'Open',
|
||||
'Waiting for Node Vote',
|
||||
'Open',
|
||||
];
|
||||
const stateColAsc = [
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
'Passed',
|
||||
'Passed',
|
||||
'Passed',
|
||||
'Waiting for Node Vote',
|
||||
'Waiting for Node Vote',
|
||||
];
|
||||
const stateColDesc = [
|
||||
'Waiting for Node Vote',
|
||||
'Waiting for Node Vote',
|
||||
'Passed',
|
||||
'Passed',
|
||||
'Passed',
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
'Open',
|
||||
];
|
||||
checkSorting('state', stateColDefault, stateColAsc, stateColDesc);
|
||||
});
|
||||
|
||||
it('can drag and drop columns', () => {
|
||||
// 6001-MARK-063
|
||||
cy.get(colMarketId).realMouseDown().realMouseMove(700, 15).realMouseUp();
|
||||
cy.get(colMarketId).should(($element) => {
|
||||
const attributeValue = $element.attr('aria-colindex');
|
||||
expect(attributeValue).not.to.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no markets proposed', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
const proposal: ProposalsListQuery = {};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'ProposalsList', proposal);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
});
|
||||
|
||||
it.skip('can see no markets message', () => {
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
|
||||
// 6001-MARK-061
|
||||
cy.getByTestId('tab-proposed-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { testOrderSubmission } from '../support/order-validation';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { createOrder } from '../support/create-order';
|
||||
|
||||
describe.skip('must submit order', { tags: '@smoke' }, () => {
|
||||
// 7002-SORD-039
|
||||
before(() => {
|
||||
cy.setVegaWallet();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
|
||||
it('successfully places market buy order', () => {
|
||||
// 7002-SORD-010
|
||||
// 0003-WTXN-012
|
||||
// 0003-WTXN-003
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
size: '100',
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order);
|
||||
});
|
||||
|
||||
it('successfully places market sell order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_IOC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order);
|
||||
});
|
||||
|
||||
it('successfully places limit buy order', () => {
|
||||
// 7002-SORD-017
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '200',
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, { price: '20000000' });
|
||||
});
|
||||
|
||||
it('successfully places limit sell order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GFN,
|
||||
size: '100',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
price: '50000',
|
||||
};
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, { price: '5000000000' });
|
||||
});
|
||||
|
||||
it('successfully places GTT limit buy order', () => {
|
||||
cy.mockVegaWalletTransaction();
|
||||
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||
const order: OrderSubmission = {
|
||||
marketId: 'market-0',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||
size: '100',
|
||||
price: '1.00',
|
||||
expiresAt: expiresAt.toISOString().substring(0, 16),
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
};
|
||||
|
||||
createOrder(order);
|
||||
testOrderSubmission(order, {
|
||||
price: '100000',
|
||||
expiresAt:
|
||||
new Date(order.expiresAt as string).getTime().toString() + '000000',
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,7 +23,7 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
||||
// 0003-WTXN-001
|
||||
cy.getByTestId('connect-vega-wallet'); // Not connected
|
||||
cy.getByTestId(placeOrderBtn).should('exist');
|
||||
cy.getByTestId('order-connect-wallet').should('exist');
|
||||
cy.getByTestId('get-started-button').should('exist');
|
||||
});
|
||||
|
||||
it('must be able to select order direction - long/short', function () {
|
||||
@@ -44,7 +44,7 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
||||
mockConnectWallet();
|
||||
cy.getByTestId(toggleLimit).click();
|
||||
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('connectors-list')
|
||||
.find('[data-testid="connector-jsonRpc"]')
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId(orderSizeField).clear().type('1');
|
||||
cy.getByTestId(orderPriceField).clear().type('1.123456');
|
||||
cy.getByTestId(placeOrderBtn).click();
|
||||
cy.getByTestId('deal-ticket-error-message-price').should(
|
||||
cy.getByTestId('deal-ticket-error-message-price-limit').should(
|
||||
'have.text',
|
||||
'Price accepts up to 5 decimal places'
|
||||
);
|
||||
@@ -87,7 +87,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId(orderSizeField).clear().type('1.234');
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId('deal-ticket-error-message-size').should(
|
||||
cy.getByTestId('deal-ticket-error-message-size-market').should(
|
||||
'have.text',
|
||||
'Size must be whole numbers for this market'
|
||||
);
|
||||
@@ -96,7 +96,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
it('must warn if order size is set to 0', function () {
|
||||
cy.getByTestId(orderSizeField).clear().type('0');
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId('deal-ticket-error-message-size').should(
|
||||
cy.getByTestId('deal-ticket-error-message-size-market').should(
|
||||
'have.text',
|
||||
'Size cannot be lower than 1'
|
||||
);
|
||||
|
||||
+2
-1
@@ -12,7 +12,8 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
@@ -12,8 +12,6 @@ NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
|
||||
|
||||
@@ -13,8 +13,6 @@ NX_VEGA_DOCS_URL=#
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
@@ -13,8 +13,6 @@ NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet-mainnet
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.20.21-core-0.71.6
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.mainnet-mirror.vega.rocks
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet-mainnet
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.20.19-core-0.71.6
|
||||
|
||||
|
||||
@@ -13,11 +13,9 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -14,8 +14,6 @@ NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
@@ -24,4 +22,4 @@ NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
@@ -15,9 +15,6 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
|
||||
@@ -1,2 +1,10 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const THROTTLE_UPDATE_TIME = 500;
|
||||
export const ONBOARDING_VIEWED_KEY = 'vega_onboarding_viewed';
|
||||
export const MAINNET_WELCOME_HEADER = t(
|
||||
'Trade cash settled futures on the fully decentralised Vega network.'
|
||||
);
|
||||
export const TESTNET_WELCOME_HEADER = t(
|
||||
'Try out trading cash settled futures on the fully decentralised Vega network (Testnet).'
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import type {
|
||||
SuccessorProposalListFieldsFragment,
|
||||
NewMarketSuccessorFieldsFragment,
|
||||
@@ -52,7 +52,7 @@ export const MarketSuccessorProposalBanner = ({
|
||||
TOKEN_PROPOSAL.replace(':id', item.id || '')
|
||||
);
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
<>
|
||||
<ExternalLink href={externalLink} key={i}>
|
||||
{
|
||||
(item.terms?.change as NewMarketSuccessorFieldsFragment)
|
||||
@@ -60,7 +60,7 @@ export const MarketSuccessorProposalBanner = ({
|
||||
}
|
||||
</ExternalLink>
|
||||
{i < successors.length - 1 && ', '}
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Header, HeaderTitle } from '../header';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { MarketSelector } from '../../components/market-selector/market-selector';
|
||||
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
|
||||
import { useMarket, useMarketList } from '@vegaprotocol/markets';
|
||||
import { useMarket } from '@vegaprotocol/markets';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const MarketHeader = () => {
|
||||
@@ -11,10 +11,6 @@ export const MarketHeader = () => {
|
||||
const { data } = useMarket(marketId);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
// Ensure that markets are kept cached so opening the list
|
||||
// shows all markets instantly
|
||||
useMarketList();
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -132,7 +132,6 @@ describe('MarketSelector', () => {
|
||||
data: markets,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
reload: jest.fn(),
|
||||
});
|
||||
|
||||
it('Button "All" should be selected by default', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import { type MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
|
||||
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
|
||||
import {
|
||||
TradingInput,
|
||||
TinyScroll,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useCallback, useState, useMemo, useRef, useEffect } from 'react';
|
||||
import { useCallback, useState, useMemo, useRef } from 'react';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import { useMarketSelectorList } from './use-market-selector-list';
|
||||
import type { ProductType } from './product-selector';
|
||||
@@ -44,12 +44,7 @@ export const MarketSelector = ({
|
||||
assets: [],
|
||||
});
|
||||
const allProducts = filter.product === Product.All;
|
||||
const { markets, data, loading, error, reload } =
|
||||
useMarketSelectorList(filter);
|
||||
|
||||
useEffect(() => {
|
||||
reload();
|
||||
}, [reload]);
|
||||
const { markets, data, loading, error } = useMarketSelectorList(filter);
|
||||
|
||||
return (
|
||||
<div data-testid="market-selector">
|
||||
|
||||
@@ -15,7 +15,6 @@ export const Sort = {
|
||||
Gained: 'Gained',
|
||||
Lost: 'Lost',
|
||||
New: 'New',
|
||||
TopTraded: 'TopTraded',
|
||||
} as const;
|
||||
|
||||
export type SortType = keyof typeof Sort;
|
||||
@@ -27,7 +26,6 @@ export const SortTypeMapping: {
|
||||
[Sort.Gained]: 'Top gaining',
|
||||
[Sort.Lost]: 'Top losing',
|
||||
[Sort.New]: 'New markets',
|
||||
[Sort.TopTraded]: 'Top traded',
|
||||
};
|
||||
|
||||
const SortIconMapping: {
|
||||
@@ -37,7 +35,6 @@ const SortIconMapping: {
|
||||
[Sort.Gained]: VegaIconNames.TREND_UP,
|
||||
[Sort.Lost]: VegaIconNames.TREND_DOWN,
|
||||
[Sort.New]: VegaIconNames.STAR,
|
||||
[Sort.TopTraded]: VegaIconNames.ARROW_UP,
|
||||
};
|
||||
|
||||
export const SortDropdown = ({
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import {
|
||||
calcCandleVolume,
|
||||
calcTradedFactor,
|
||||
useMarketList,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { calcCandleVolume, useMarketList } from '@vegaprotocol/markets';
|
||||
import { priceChangePercentage } from '@vegaprotocol/utils';
|
||||
import type { Filter } from '../../components/market-selector/market-selector';
|
||||
import { Sort } from './sort-dropdown';
|
||||
@@ -24,7 +20,7 @@ export const useMarketSelectorList = ({
|
||||
sort,
|
||||
searchTerm,
|
||||
}: Filter) => {
|
||||
const { data, loading, error, reload } = useMarketList();
|
||||
const { data, loading, error } = useMarketList();
|
||||
|
||||
const markets = useMemo(() => {
|
||||
if (!data?.length) return [];
|
||||
@@ -98,14 +94,10 @@ export const useMarketSelectorList = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (sort === Sort.TopTraded) {
|
||||
return orderBy(markets, [(m) => calcTradedFactor(m)], ['desc']);
|
||||
}
|
||||
|
||||
return markets;
|
||||
}, [data, product, searchTerm, assets, sort]);
|
||||
|
||||
return { markets, data, loading, error, reload };
|
||||
return { markets, data, loading, error };
|
||||
};
|
||||
|
||||
export const isMarketActive = (state: MarketState) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketSelector } from '../market-selector';
|
||||
import { useMarket, useMarketList } from '@vegaprotocol/markets';
|
||||
import { useMarket } from '@vegaprotocol/markets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
@@ -14,10 +14,6 @@ export const NavHeader = () => {
|
||||
const { data } = useMarket(marketId);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
// Ensure that markets are kept cached so opening the list
|
||||
// shows all markets instantly
|
||||
useMarketList();
|
||||
|
||||
if (!marketId) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -36,6 +36,6 @@ export const StopOrdersContainer = () => {
|
||||
|
||||
const useStopOrdersStore = create<DataGridSlice>()(
|
||||
persist(createDataGridSlice, {
|
||||
name: 'vega_stop_orders_store',
|
||||
name: 'vega_fills_store',
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,36 +1,16 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { GetStarted } from './get-started';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
let mockStep = 1;
|
||||
jest.mock('./use-get-onboarding-step', () => ({
|
||||
...jest.requireActual('./use-get-onboarding-step'),
|
||||
useGetOnboardingStep: jest.fn(() => mockStep),
|
||||
}));
|
||||
|
||||
describe('GetStarted', () => {
|
||||
const renderComponent = (context: Partial<VegaWalletContextShape> = {}) => {
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
|
||||
<GetStarted />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
|
||||
<GetStarted />
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
};
|
||||
const checkTicks = (elements: Element[]) => {
|
||||
elements.forEach((item, i) => {
|
||||
if (i + 1 < mockStep) {
|
||||
expect(item.querySelector('[data-testid="icon-tick"]')).toBeTruthy();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders full get started content if not connected and no browser wallet detected', () => {
|
||||
renderComponent();
|
||||
@@ -40,71 +20,12 @@ describe('GetStarted', () => {
|
||||
it('renders connect prompt if no pubKey but wallet installed', () => {
|
||||
globalThis.window.vega = {} as Vega;
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('get-started-banner')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('order-connect-wallet')).toBeInTheDocument();
|
||||
globalThis.window.vega = undefined as unknown as Vega;
|
||||
});
|
||||
|
||||
it('renders nothing if connected', () => {
|
||||
mockStep = 0;
|
||||
const { container } = renderComponent({ pubKey: 'my-pubkey' });
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('steps should be ticked', () => {
|
||||
const navigatorGetter: jest.SpyInstance = jest.spyOn(
|
||||
window.navigator,
|
||||
'userAgent',
|
||||
'get'
|
||||
);
|
||||
navigatorGetter.mockReturnValue('Chrome');
|
||||
mockStep = 1;
|
||||
const { rerender, container } = renderComponent();
|
||||
expect(screen.queryByTestId('icon-tick')).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId('get-wallet-button')).toBeInTheDocument();
|
||||
|
||||
mockStep = 2;
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
<VegaWalletContext.Provider value={{} as VegaWalletContextShape}>
|
||||
<GetStarted />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
checkTicks(screen.getAllByRole('listitem'));
|
||||
expect(screen.getByRole('button', { name: 'Connect' })).toBeInTheDocument();
|
||||
|
||||
mockStep = 3;
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
<VegaWalletContext.Provider value={{} as VegaWalletContextShape}>
|
||||
<GetStarted />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
checkTicks(screen.getAllByRole('listitem'));
|
||||
expect(screen.getByRole('button', { name: 'Deposit' })).toBeInTheDocument();
|
||||
|
||||
mockStep = 4;
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
<VegaWalletContext.Provider value={{} as VegaWalletContextShape}>
|
||||
<GetStarted />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
checkTicks(screen.getAllByRole('listitem'));
|
||||
expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
|
||||
|
||||
mockStep = 5;
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
<VegaWalletContext.Provider
|
||||
value={{ pubKey: 'my-pubkey' } as VegaWalletContextShape}
|
||||
>
|
||||
<GetStarted />
|
||||
</VegaWalletContext.Provider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,96 +1,36 @@
|
||||
import classNames from 'classnames';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ExternalLink, Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
TradingButton,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
GetWalletButton,
|
||||
useVegaWallet,
|
||||
useVegaWalletDialogStore,
|
||||
isBrowserWalletInstalled,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
OnboardingStep,
|
||||
useGetOnboardingStep,
|
||||
} from './use-get-onboarding-step';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useSidebar, ViewType } from '../sidebar';
|
||||
import * as constants from '../constants';
|
||||
|
||||
interface Props {
|
||||
lead?: string;
|
||||
}
|
||||
|
||||
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
const navigate = useNavigate();
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
const link = marketId ? Links[Routes.MARKET](marketId) : Links[Routes.HOME]();
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
let buttonText = t('Get started');
|
||||
let onClickHandle = () => {
|
||||
openVegaWalletDialog();
|
||||
};
|
||||
if (step === OnboardingStep.ONBOARDING_WALLET_STEP) {
|
||||
return <GetWalletButton className="justify-between" />;
|
||||
} else if (step === OnboardingStep.ONBOARDING_CONNECT_STEP) {
|
||||
buttonText = t('Connect');
|
||||
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
|
||||
buttonText = t('Deposit');
|
||||
onClickHandle = () => {
|
||||
navigate(link);
|
||||
setView({ type: ViewType.Deposit });
|
||||
update({ onBoardingDismissed: true });
|
||||
};
|
||||
} else if (step === OnboardingStep.ONBOARDING_ORDER_STEP) {
|
||||
buttonText = t('Dismiss');
|
||||
onClickHandle = () => {
|
||||
navigate(link);
|
||||
setView({ type: ViewType.Order });
|
||||
setOnboardingViewed('true');
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
onClick={onClickHandle}
|
||||
size="small"
|
||||
data-testid="get-started-button"
|
||||
intent={Intent.Info}
|
||||
>
|
||||
{buttonText}
|
||||
</TradingButton>
|
||||
);
|
||||
};
|
||||
|
||||
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 [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
|
||||
const currentStep = useGetOnboardingStep();
|
||||
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
|
||||
const getStartedNeeded =
|
||||
onBoardingViewed !== 'true' &&
|
||||
currentStep &&
|
||||
currentStep < OnboardingStep.ONBOARDING_COMPLETE_STEP;
|
||||
const onButtonClick = () => {
|
||||
openVegaWalletDialog();
|
||||
setOnboardingViewed('true');
|
||||
};
|
||||
|
||||
const wrapperClasses = classNames(
|
||||
'flex flex-col py-4 px-6 gap-4 rounded',
|
||||
@@ -99,39 +39,27 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
{ 'mt-8': !lead }
|
||||
);
|
||||
|
||||
if (getStartedNeeded) {
|
||||
if (!pubKey && !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-none">
|
||||
<Step
|
||||
step={1}
|
||||
text={t('Get a Vega wallet')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_WALLET_STEP}
|
||||
/>
|
||||
<Step
|
||||
step={2}
|
||||
text={t('Connect')}
|
||||
complete={Boolean(
|
||||
currentStep > OnboardingStep.ONBOARDING_CONNECT_STEP || pubKey
|
||||
)}
|
||||
/>
|
||||
<Step
|
||||
step={3}
|
||||
text={t('Deposit funds')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_DEPOSIT_STEP}
|
||||
/>
|
||||
<Step
|
||||
step={4}
|
||||
text={t('Open a position')}
|
||||
complete={currentStep > OnboardingStep.ONBOARDING_ORDER_STEP}
|
||||
/>
|
||||
<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>
|
||||
<GetStartedButton step={currentStep} />
|
||||
<TradingButton
|
||||
intent={Intent.Info}
|
||||
onClick={onButtonClick}
|
||||
data-testid="get-started-button"
|
||||
>
|
||||
{t('Get started')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
{VEGA_ENV === Networks.MAINNET && (
|
||||
<p className="text-sm">
|
||||
@@ -156,7 +84,7 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<p className="mb-1 text-sm">
|
||||
<p className="text-sm mb-1">
|
||||
You need a{' '}
|
||||
<ExternalLink href="https://vega.xyz/wallet">
|
||||
Vega wallet
|
||||
@@ -177,34 +105,3 @@ export const GetStarted = ({ lead }: Props) => {
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Step = ({
|
||||
step,
|
||||
text,
|
||||
complete,
|
||||
}: {
|
||||
step: number;
|
||||
text: string;
|
||||
complete: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<li
|
||||
className={classNames('flex', {
|
||||
'text-vega-clight-200 dark:text-vega-cdark-200': complete,
|
||||
})}
|
||||
>
|
||||
<div className="flex justify-center w-5">
|
||||
{complete ? <Tick /> : <span>{step}.</span>}
|
||||
</div>
|
||||
<div className="ml-1">{text}</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
const Tick = () => {
|
||||
return (
|
||||
<span className="relative right-[2px]">
|
||||
<VegaIcon name={VegaIconNames.TICK} size={18} />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import {
|
||||
useGetOnboardingStep,
|
||||
OnboardingStep,
|
||||
} from './use-get-onboarding-step';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts';
|
||||
import { ordersWithMarketProvider } from '@vegaprotocol/orders';
|
||||
import { positionsDataProvider } from '@vegaprotocol/positions';
|
||||
|
||||
let mockData: object[] | null = [{ id: 'item-id' }];
|
||||
jest.mock('@vegaprotocol/data-provider', () => ({
|
||||
...jest.requireActual('@vegaprotocol/data-provider'),
|
||||
useDataProvider: jest.fn(() => ({ data: mockData })),
|
||||
}));
|
||||
|
||||
let mockContext: Partial<VegaWalletContextShape> = { pubKey: 'test-pubkey' };
|
||||
|
||||
describe('useGetOnboardingStep', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockData = [{ id: 'item-id' }];
|
||||
mockContext = { pubKey: 'test-pubkey' };
|
||||
globalThis.window.vega = {} as Vega;
|
||||
});
|
||||
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<VegaWalletContext.Provider
|
||||
value={mockContext as unknown as VegaWalletContextShape}
|
||||
>
|
||||
{children}
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
it('should return properly ONBOARDING_UNKNOWN_STEP', () => {
|
||||
mockData = null;
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
expect(result.current).toEqual(OnboardingStep.ONBOARDING_UNKNOWN_STEP);
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_WALLET_STEP', () => {
|
||||
// @ts-ignore test only purpose
|
||||
globalThis.window.vega = undefined;
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
expect(result.current).toEqual(OnboardingStep.ONBOARDING_WALLET_STEP);
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_CONNECT_STEP', () => {
|
||||
mockContext = { pubKey: null };
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
expect(result.current).toEqual(OnboardingStep.ONBOARDING_CONNECT_STEP);
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_DEPOSIT_STEP', async () => {
|
||||
(useDataProvider as jest.Mock).mockImplementation((args) => {
|
||||
if (
|
||||
args.dataProvider === depositsProvider ||
|
||||
args.dataProvider === aggregatedAccountsDataProvider
|
||||
) {
|
||||
return { data: [] };
|
||||
}
|
||||
return { data: mockData };
|
||||
});
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
await expect(result.current).toEqual(
|
||||
OnboardingStep.ONBOARDING_DEPOSIT_STEP
|
||||
);
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_ORDER_STEP', async () => {
|
||||
(useDataProvider as jest.Mock).mockImplementation((args) => {
|
||||
if (
|
||||
args.dataProvider === ordersWithMarketProvider ||
|
||||
args.dataProvider === positionsDataProvider
|
||||
) {
|
||||
return { data: [] };
|
||||
}
|
||||
return { data: mockData };
|
||||
});
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
await expect(result.current).toEqual(OnboardingStep.ONBOARDING_ORDER_STEP);
|
||||
});
|
||||
|
||||
it('should return properly ONBOARDING_COMPLETE_STEP', async () => {
|
||||
(useDataProvider as jest.Mock).mockImplementation(() => {
|
||||
return { data: mockData };
|
||||
});
|
||||
const { result } = renderHook(() => useGetOnboardingStep(), { wrapper });
|
||||
await expect(result.current).toEqual(
|
||||
OnboardingStep.ONBOARDING_COMPLETE_STEP
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,82 +0,0 @@
|
||||
import { isBrowserWalletInstalled, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { ordersWithMarketProvider } from '@vegaprotocol/orders';
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
import { aggregatedAccountsDataProvider } from '@vegaprotocol/accounts';
|
||||
import { positionsDataProvider } from '@vegaprotocol/positions';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
export enum OnboardingStep {
|
||||
ONBOARDING_UNKNOWN_STEP,
|
||||
ONBOARDING_WALLET_STEP,
|
||||
ONBOARDING_CONNECT_STEP,
|
||||
ONBOARDING_DEPOSIT_STEP,
|
||||
ONBOARDING_ORDER_STEP,
|
||||
ONBOARDING_COMPLETE_STEP,
|
||||
}
|
||||
|
||||
export const useGetOnboardingStep = () => {
|
||||
const connecting = useGlobalStore((store) => store.eagerConnecting);
|
||||
const { pubKey = '', pubKeys } = useVegaWallet();
|
||||
const { data: depositsData } = useDataProvider({
|
||||
dataProvider: depositsProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
const { data: collateralData } = useDataProvider({
|
||||
dataProvider: aggregatedAccountsDataProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
const collaterals = Boolean(collateralData?.length);
|
||||
const deposits =
|
||||
depositsData?.some(
|
||||
(item) => item.status === Types.DepositStatus.STATUS_FINALIZED
|
||||
) || false;
|
||||
const { data: ordersData } = useDataProvider({
|
||||
dataProvider: ordersWithMarketProvider,
|
||||
variables: {
|
||||
partyId: pubKey || '',
|
||||
pagination: {
|
||||
first: 1,
|
||||
},
|
||||
},
|
||||
skip: !pubKey,
|
||||
});
|
||||
const orders = Boolean(ordersData?.length);
|
||||
|
||||
const partyIds = pubKeys?.map((item) => item.publicKey) || [];
|
||||
const { data: positionsData } = useDataProvider({
|
||||
dataProvider: positionsDataProvider,
|
||||
variables: {
|
||||
partyIds,
|
||||
},
|
||||
skip: !partyIds?.length,
|
||||
});
|
||||
const positions = Boolean(positionsData?.length);
|
||||
|
||||
const isLoading = Boolean(
|
||||
(connecting || pubKey) &&
|
||||
(depositsData === null ||
|
||||
ordersData === null ||
|
||||
collateralData === null ||
|
||||
positionsData === null)
|
||||
);
|
||||
if (isLoading) {
|
||||
return OnboardingStep.ONBOARDING_UNKNOWN_STEP;
|
||||
}
|
||||
if (!isBrowserWalletInstalled()) {
|
||||
return OnboardingStep.ONBOARDING_WALLET_STEP;
|
||||
}
|
||||
if (!pubKey) {
|
||||
return OnboardingStep.ONBOARDING_CONNECT_STEP;
|
||||
}
|
||||
if (!deposits && !collaterals) {
|
||||
return OnboardingStep.ONBOARDING_DEPOSIT_STEP;
|
||||
}
|
||||
if (!orders && !positions) {
|
||||
return OnboardingStep.ONBOARDING_ORDER_STEP;
|
||||
}
|
||||
return OnboardingStep.ONBOARDING_COMPLETE_STEP;
|
||||
};
|
||||
@@ -3,19 +3,21 @@ 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';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
export const WelcomeDialogContent = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
const browseMarkets = () => {
|
||||
const link = Links[Routes.MARKETS]();
|
||||
navigate(link);
|
||||
update({ onBoardingDismissed: true });
|
||||
setOnboardingViewed('true');
|
||||
};
|
||||
const lead =
|
||||
VEGA_ENV === Networks.MAINNET
|
||||
@@ -55,7 +57,7 @@ export const WelcomeDialogContent = () => {
|
||||
{t('Browse the markets')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
<div className="sm:w-1/2 flex grow">
|
||||
<div className="sm:w-1/2">
|
||||
<GetStarted lead={lead} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,38 +2,31 @@ import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { isBrowserWalletInstalled } from '@vegaprotocol/wallet';
|
||||
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';
|
||||
import {
|
||||
useGetOnboardingStep,
|
||||
OnboardingStep,
|
||||
} from './use-get-onboarding-step';
|
||||
import * as constants from '../constants';
|
||||
|
||||
export const WelcomeDialog = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const dismissed = useGlobalStore((store) => store.onBoardingDismissed);
|
||||
const currentStep = useGetOnboardingStep();
|
||||
|
||||
const [onBoardingViewed, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
const isOnboardingDialogNeeded =
|
||||
onBoardingViewed !== 'true' &&
|
||||
currentStep &&
|
||||
currentStep < OnboardingStep.ONBOARDING_COMPLETE_STEP &&
|
||||
!dismissed;
|
||||
onBoardingViewed !== 'true' && !isBrowserWalletInstalled() && !getConfig();
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const onClose = () => {
|
||||
setOnboardingViewed('true');
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.HOME]();
|
||||
navigate(link);
|
||||
update({ onBoardingDismissed: true });
|
||||
};
|
||||
const title = (
|
||||
<span className="font-alpha calt" data-testid="welcome-title">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import type { AppProps } from 'next/app';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
useNodeSwitcherStore,
|
||||
} from '@vegaprotocol/environment';
|
||||
import './styles.css';
|
||||
import { useGlobalStore, usePageTitleStore } from '../stores';
|
||||
import { usePageTitleStore } from '../stores';
|
||||
import DialogsContainer from './dialogs-container';
|
||||
import ToastsManager from './toasts-manager';
|
||||
import {
|
||||
@@ -170,8 +170,7 @@ const PartyData = () => {
|
||||
|
||||
const MaybeConnectEagerly = () => {
|
||||
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const eagerConnecting = useVegaEagerConnect(Connectors);
|
||||
useVegaEagerConnect(Connectors);
|
||||
const [isTelemetryApproved] = useTelemetryApproval();
|
||||
useEthereumEagerConnect(
|
||||
isTelemetryApproved ? { dsn: SENTRY_DSN, env: VEGA_ENV } : {}
|
||||
@@ -183,8 +182,5 @@ const MaybeConnectEagerly = () => {
|
||||
if (query && !pubKey) {
|
||||
connect(Connectors['view']);
|
||||
}
|
||||
useEffect(() => {
|
||||
update({ eagerConnecting });
|
||||
}, [update, eagerConnecting]);
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -15,10 +15,6 @@ body,
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
@apply tracking-tighter;
|
||||
}
|
||||
|
||||
.text-default {
|
||||
@apply text-vega-clight-50 dark:text-vega-cdark-50;
|
||||
}
|
||||
@@ -151,7 +147,7 @@ html [data-theme='dark'] {
|
||||
}
|
||||
|
||||
.vega-ag-grid .ag-header-row {
|
||||
@apply font-normal font-alpha;
|
||||
@apply font-alpha font-normal;
|
||||
}
|
||||
|
||||
/* Light variables */
|
||||
@@ -213,15 +209,3 @@ html [data-theme='dark'] {
|
||||
box-shadow: inset 0 0 6px rgb(0 0 0 / 30%);
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import produce from 'immer';
|
||||
|
||||
interface GlobalStore {
|
||||
marketId: string | null;
|
||||
onBoardingDismissed: boolean;
|
||||
eagerConnecting: boolean;
|
||||
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
|
||||
}
|
||||
|
||||
@@ -16,8 +14,6 @@ interface PageTitleStore {
|
||||
|
||||
export const useGlobalStore = create<GlobalStore>()((set) => ({
|
||||
marketId: LocalStorage.getItem('marketId') || null,
|
||||
onBoardingDismissed: false,
|
||||
eagerConnecting: false,
|
||||
update: (newState) => {
|
||||
set(
|
||||
produce((state: GlobalStore) => {
|
||||
|
||||
@@ -153,10 +153,8 @@ export function waitForProposal(id: string): Promise<{ id: string }> {
|
||||
try {
|
||||
const res = await getProposal(id);
|
||||
if (
|
||||
(res.proposal !== null &&
|
||||
res.proposal.state === Schema.ProposalState.STATE_OPEN) ||
|
||||
res.proposal.state ===
|
||||
Schema.ProposalState.STATE_WAITING_FOR_NODE_VOTE
|
||||
res.proposal !== null &&
|
||||
res.proposal.state === Schema.ProposalState.STATE_OPEN
|
||||
) {
|
||||
clearInterval(interval);
|
||||
resolve(res.proposal);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { Control } from 'react-hook-form';
|
||||
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
|
||||
import { DealTicketMarketAmount } from './deal-ticket-market-amount';
|
||||
import { DealTicketLimitAmount } from './deal-ticket-limit-amount';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { OrderFormValues } from '../../hooks/use-form-values';
|
||||
|
||||
export interface DealTicketAmountProps {
|
||||
control: Control<OrderFormValues>;
|
||||
type: Schema.OrderType;
|
||||
marketData: StaticMarketData;
|
||||
marketPrice?: string;
|
||||
market: Market;
|
||||
sizeError?: string;
|
||||
priceError?: string;
|
||||
}
|
||||
|
||||
export const DealTicketAmount = ({
|
||||
type,
|
||||
marketData,
|
||||
marketPrice,
|
||||
...props
|
||||
}: DealTicketAmountProps) => {
|
||||
switch (type) {
|
||||
case Schema.OrderType.TYPE_MARKET:
|
||||
return (
|
||||
<DealTicketMarketAmount
|
||||
{...props}
|
||||
marketData={marketData}
|
||||
marketPrice={marketPrice}
|
||||
/>
|
||||
);
|
||||
case Schema.OrderType.TYPE_LIMIT:
|
||||
return <DealTicketLimitAmount {...props} />;
|
||||
default: {
|
||||
throw new Error('Invalid ticket type ' + type);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
import {
|
||||
TradingFormGroup,
|
||||
TradingInput,
|
||||
TradingInputError,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||
import { Controller } from 'react-hook-form';
|
||||
|
||||
export type DealTicketLimitAmountProps = Omit<
|
||||
DealTicketAmountProps,
|
||||
'marketData' | 'type'
|
||||
>;
|
||||
|
||||
export const DealTicketLimitAmount = ({
|
||||
control,
|
||||
market,
|
||||
sizeError,
|
||||
priceError,
|
||||
}: DealTicketLimitAmountProps) => {
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
const renderError = () => {
|
||||
if (sizeError) {
|
||||
return (
|
||||
<TradingInputError testId="deal-ticket-error-message-size-limit">
|
||||
{sizeError}
|
||||
</TradingInputError>
|
||||
);
|
||||
}
|
||||
|
||||
if (priceError) {
|
||||
return (
|
||||
<TradingInputError testId="deal-ticket-error-message-price-limit">
|
||||
{priceError}
|
||||
</TradingInputError>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-1">
|
||||
<TradingFormGroup
|
||||
label={t('Size')}
|
||||
labelFor="input-order-size-limit"
|
||||
className="!mb-0"
|
||||
>
|
||||
<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, fieldState }) => (
|
||||
<TradingInput
|
||||
id="input-order-size-limit"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
data-testid="order-size"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
hasError={!!fieldState.error}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
</div>
|
||||
<div className="pt-5 leading-10">@</div>
|
||||
<div className="flex-1">
|
||||
<TradingFormGroup
|
||||
labelFor="input-price-quote"
|
||||
label={t(`Price (${quoteName})`)}
|
||||
labelAlign="right"
|
||||
className="!mb-0"
|
||||
>
|
||||
<Controller
|
||||
name="price"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need provide a price'),
|
||||
min: {
|
||||
value: priceStep,
|
||||
message: t('Price cannot be lower than ' + priceStep),
|
||||
},
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<TradingInput
|
||||
id="input-price-quote"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
data-testid="order-price"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
hasError={!!fieldState.error}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
</div>
|
||||
</div>
|
||||
{renderError()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
toDecimal,
|
||||
validateAmount,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
TradingInput,
|
||||
TradingInputError,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { isMarketInAuction } from '@vegaprotocol/markets';
|
||||
import type { DealTicketAmountProps } from './deal-ticket-amount';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export type DealTicketMarketAmountProps = Omit<DealTicketAmountProps, 'type'>;
|
||||
|
||||
export const DealTicketMarketAmount = ({
|
||||
control,
|
||||
market,
|
||||
marketData,
|
||||
marketPrice,
|
||||
sizeError,
|
||||
}: DealTicketMarketAmountProps) => {
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const price = marketPrice;
|
||||
|
||||
const priceFormatted = price
|
||||
? addDecimalsFormatNumber(price, market.decimalPlaces)
|
||||
: undefined;
|
||||
|
||||
const inAuction = isMarketInAuction(marketData.marketTradingMode);
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="mb-2 text-xs">{t('Size')}</div>
|
||||
<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, fieldState }) => (
|
||||
<TradingInput
|
||||
id="input-order-size-market"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
data-testid="order-size"
|
||||
hasError={!!fieldState.error}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-5 leading-10">@</div>
|
||||
<div className="flex-1 text-sm text-right">
|
||||
{inAuction && (
|
||||
<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 className="mb-2">{t(`Indicative price`)}</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div
|
||||
data-testid="last-price"
|
||||
className={classNames('leading-10', { 'pt-5': !inAuction })}
|
||||
>
|
||||
{priceFormatted && quoteName ? (
|
||||
<>
|
||||
~{priceFormatted} {quoteName}
|
||||
</>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{sizeError && (
|
||||
<TradingInputError
|
||||
intent="danger"
|
||||
testId="deal-ticket-error-message-size-market"
|
||||
>
|
||||
{sizeError}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -72,7 +72,6 @@ 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 triggerPriceWarningMessage = 'stop-order-warning-message-trigger-price';
|
||||
const triggerTrailingPercentOffsetErrorMessage =
|
||||
'stop-order-error-message-trigger-trailing-percent-offset';
|
||||
|
||||
@@ -115,6 +114,14 @@ describe('StopOrder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
@@ -196,8 +203,8 @@ describe('StopOrder', () => {
|
||||
|
||||
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.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();
|
||||
@@ -242,17 +249,10 @@ describe('StopOrder', () => {
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.001');
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using value causing immediate trigger
|
||||
// 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();
|
||||
expect(
|
||||
screen.queryByTestId(triggerPriceWarningMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// change to correct value
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '2');
|
||||
expect(screen.queryByTestId(triggerPriceWarningMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates trigger trailing percentage offset field', async () => {
|
||||
|
||||
@@ -2,22 +2,21 @@ import { useRef, useCallback, useEffect } from 'react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { StopOrdersSubmission } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
formatForInput,
|
||||
formatNumber,
|
||||
removeDecimal,
|
||||
toDecimal,
|
||||
validateAmount,
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { Control, UseFormWatch } from 'react-hook-form';
|
||||
import { useForm, Controller, useController } from 'react-hook-form';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
TradingRadio as Radio,
|
||||
TradingRadioGroup as RadioGroup,
|
||||
TradingInput as Input,
|
||||
TradingCheckbox as Checkbox,
|
||||
TradingFormGroup as FormGroup,
|
||||
TradingInputError as InputError,
|
||||
TradingSelect as Select,
|
||||
TradingRadio,
|
||||
TradingRadioGroup,
|
||||
TradingInput,
|
||||
TradingCheckbox,
|
||||
TradingFormGroup,
|
||||
TradingInputError,
|
||||
TradingSelect,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { getDerivedPrice, type Market } from '@vegaprotocol/markets';
|
||||
@@ -50,8 +49,6 @@ export interface StopOrderProps {
|
||||
submit: (order: StopOrdersSubmission) => void;
|
||||
}
|
||||
|
||||
const trailingPercentOffsetStep = '0.1';
|
||||
|
||||
const getDefaultValues = (
|
||||
type: Schema.OrderType,
|
||||
storedValues?: Partial<StopOrderFormValues>
|
||||
@@ -65,426 +62,9 @@ const getDefaultValues = (
|
||||
expire: false,
|
||||
expiryStrategy: Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT,
|
||||
size: '0',
|
||||
oco: false,
|
||||
ocoType: type,
|
||||
ocoTimeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
ocoTriggerType: 'price',
|
||||
ocoSize: '0',
|
||||
...storedValues,
|
||||
});
|
||||
|
||||
const Trigger = ({
|
||||
control,
|
||||
watch,
|
||||
priceStep,
|
||||
assetSymbol,
|
||||
oco,
|
||||
marketPrice,
|
||||
decimalPlaces,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
watch: UseFormWatch<StopOrderFormValues>;
|
||||
priceStep: string;
|
||||
assetSymbol: string;
|
||||
oco?: boolean;
|
||||
marketPrice?: string | null;
|
||||
decimalPlaces: number;
|
||||
}) => {
|
||||
const triggerType = watch(oco ? 'ocoTriggerType' : 'triggerType');
|
||||
const triggerDirection = watch('triggerDirection');
|
||||
const isPriceTrigger = triggerType === 'price';
|
||||
return (
|
||||
<FormGroup label={t('Trigger')} labelFor="">
|
||||
<Controller
|
||||
name="triggerDirection"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const { value, onChange } = field;
|
||||
return (
|
||||
<RadioGroup
|
||||
name="triggerDirection"
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
className="mb-2"
|
||||
>
|
||||
<Radio
|
||||
value={
|
||||
oco
|
||||
? Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
: Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
}
|
||||
id={`triggerDirection-risesAbove${oco ? '-oco' : ''}`}
|
||||
label={'Rises above'}
|
||||
/>
|
||||
<Radio
|
||||
value={
|
||||
!oco
|
||||
? Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
: Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
}
|
||||
id={`triggerDirection-fallsBelow${oco ? '-oco' : ''}`}
|
||||
label={'Falls below'}
|
||||
/>
|
||||
</RadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{isPriceTrigger && (
|
||||
<div className="mb-2">
|
||||
<Controller
|
||||
name={oco ? 'ocoTriggerPrice' : '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, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
let triggerWarning = false;
|
||||
|
||||
if (marketPrice && value) {
|
||||
const condition =
|
||||
(!oco &&
|
||||
triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_RISES_ABOVE) ||
|
||||
(oco &&
|
||||
triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_FALLS_BELOW)
|
||||
? '>'
|
||||
: '<';
|
||||
const diff =
|
||||
BigInt(marketPrice) -
|
||||
BigInt(removeDecimal(value, decimalPlaces));
|
||||
if (
|
||||
(condition === '>' && diff > 0) ||
|
||||
(condition === '<' && diff < 0)
|
||||
) {
|
||||
triggerWarning = true;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<Input
|
||||
data-testid={`triggerPrice${oco ? '-oco' : ''}`}
|
||||
type="number"
|
||||
step={priceStep}
|
||||
appendElement={assetSymbol}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
{fieldState.error && (
|
||||
<InputError
|
||||
testId={`stop-order-error-message-trigger-price${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
{!fieldState.error && triggerWarning && (
|
||||
<InputError
|
||||
intent="warning"
|
||||
testId={`stop-order-warning-message-trigger-price${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
>
|
||||
{t('Stop order will be triggered immediately')}
|
||||
</InputError>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!isPriceTrigger && (
|
||||
<div className="mb-2">
|
||||
<Controller
|
||||
name={
|
||||
oco
|
||||
? 'ocoTriggerTrailingPercentOffset'
|
||||
: '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, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<Input
|
||||
type="number"
|
||||
step={trailingPercentOffsetStep}
|
||||
appendElement="%"
|
||||
data-testid={`triggerTrailingPercentOffset${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
{fieldState.error && (
|
||||
<InputError
|
||||
testId={`stop-order-error-message-trigger-trailing-percent-offset${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Controller
|
||||
name={oco ? 'ocoTriggerType' : 'triggerType'}
|
||||
control={control}
|
||||
rules={{
|
||||
deps: oco
|
||||
? ['ocoTriggerTrailingPercentOffset', 'ocoTriggerPrice']
|
||||
: ['triggerTrailingPercentOffset', 'triggerPrice'],
|
||||
}}
|
||||
render={({ field }) => {
|
||||
const { onChange, value } = field;
|
||||
return (
|
||||
<RadioGroup
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Radio
|
||||
value="price"
|
||||
id={`triggerType-price${oco ? '-oco' : ''}`}
|
||||
label={'Price'}
|
||||
/>
|
||||
<Radio
|
||||
value="trailingPercentOffset"
|
||||
id={`triggerType-trailingPercentOffset${oco ? '-oco' : ''}`}
|
||||
label={'Trailing Percent Offset'}
|
||||
/>
|
||||
</RadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const Size = ({
|
||||
control,
|
||||
sizeStep,
|
||||
oco,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
sizeStep: string;
|
||||
oco?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Controller
|
||||
name={oco ? 'ocoSize' : '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, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
const id = `order-size${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<FormGroup labelFor={id} label={t(`Size`)} compact>
|
||||
<Input
|
||||
id={id}
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
data-testid={id}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</FormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError
|
||||
testId={`stop-order-error-message-size${oco ? '-oco' : ''}`}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Price = ({
|
||||
control,
|
||||
watch,
|
||||
priceStep,
|
||||
quoteName,
|
||||
oco,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
watch: UseFormWatch<StopOrderFormValues>;
|
||||
priceStep: string;
|
||||
quoteName: string;
|
||||
oco?: boolean;
|
||||
}) => {
|
||||
if (watch(oco ? 'ocoType' : 'type') === Schema.OrderType.TYPE_MARKET) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Controller
|
||||
name={oco ? 'ocoPrice' : '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, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
const id = `order-price${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
labelFor={id}
|
||||
label={t(`Price (${quoteName})`)}
|
||||
compact={true}
|
||||
>
|
||||
<Input
|
||||
id={id}
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
data-testid={id}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</FormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError
|
||||
testId={`stop-order-error-message-price${oco ? '-oco' : ''}`}
|
||||
>
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TimeInForce = ({
|
||||
control,
|
||||
oco,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
oco?: boolean;
|
||||
}) => (
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => {
|
||||
const id = `select-time-in-force${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<FormGroup label={t('Time in force')} labelFor={id} compact={true}>
|
||||
<Select
|
||||
id={id}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
hasError={!!fieldState.error}
|
||||
{...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>
|
||||
{fieldState.error && (
|
||||
<InputError testId={`stop-error-message-tif${oco ? '-oco' : ''}`}>
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const ReduceOnly = () => (
|
||||
<Checkbox
|
||||
name="reduce-only"
|
||||
checked={true}
|
||||
disabled={true}
|
||||
label={
|
||||
<Tooltip description={<span>{t(REDUCE_ONLY_TOOLTIP)}</span>}>
|
||||
<>{t('Reduce only')}</>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const setType = useDealTicketFormValues((state) => state.setType);
|
||||
@@ -527,8 +107,6 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
const timeInForce = watch('timeInForce');
|
||||
const rawPrice = watch('price');
|
||||
const rawSize = watch('size');
|
||||
const oco = watch('oco');
|
||||
const expiresAt = watch('expiresAt');
|
||||
|
||||
useEffect(() => {
|
||||
const size = storedFormValues?.[dealTicketType]?.size;
|
||||
@@ -577,6 +155,12 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
const trailingPercentOffsetStep = '0.1';
|
||||
|
||||
const priceFormatted =
|
||||
isPriceTrigger && triggerPrice
|
||||
? formatNumber(triggerPrice, market.decimalPlaces)
|
||||
: undefined;
|
||||
|
||||
useController({
|
||||
name: 'type',
|
||||
@@ -603,9 +187,9 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
}}
|
||||
/>
|
||||
{errors.type && (
|
||||
<InputError testId="stop-order-error-message-type">
|
||||
<TradingInputError testId="stop-order-error-message-type">
|
||||
{errors.type.message}
|
||||
</InputError>
|
||||
</TradingInputError>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
@@ -615,128 +199,303 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
<SideSelector value={field.value} onValueChange={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
<Trigger
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
assetSymbol={asset.symbol}
|
||||
marketPrice={marketPrice}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
/>
|
||||
<hr className="mb-4 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<Price
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
quoteName={quoteName}
|
||||
/>
|
||||
<Size control={control} sizeStep={sizeStep} />
|
||||
<TimeInForce control={control} />
|
||||
<div className="flex gap-2 pb-3 justify-end">
|
||||
<ReduceOnly />
|
||||
</div>
|
||||
<hr className="mb-4 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<div className="flex gap-2 pb-2 justify-between">
|
||||
<TradingFormGroup label={t('Trigger')} compact={true} labelFor="">
|
||||
<Controller
|
||||
name="oco"
|
||||
name="triggerDirection"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const { onChange, value } = field;
|
||||
return (
|
||||
<Checkbox
|
||||
onCheckedChange={(state) => {
|
||||
onChange(state);
|
||||
setValue(
|
||||
'expiryStrategy',
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS
|
||||
);
|
||||
}}
|
||||
checked={value}
|
||||
name="oco"
|
||||
label={
|
||||
<Tooltip
|
||||
description={<span>{t('One cancels another')}</span>}
|
||||
>
|
||||
<>{t('OCO')}</>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<TradingRadioGroup
|
||||
name="triggerDirection"
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
className="mb-2"
|
||||
>
|
||||
<TradingRadio
|
||||
value={
|
||||
Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
}
|
||||
id="triggerDirection-risesAbove"
|
||||
label={'Rises above'}
|
||||
/>
|
||||
<TradingRadio
|
||||
value={
|
||||
Schema.StopOrderTriggerDirection
|
||||
.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
}
|
||||
id="triggerDirection-fallsBelow"
|
||||
label={'Falls below'}
|
||||
/>
|
||||
</TradingRadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{oco && (
|
||||
<>
|
||||
<FormGroup label={t('Type')} labelFor="">
|
||||
{isPriceTrigger && (
|
||||
<div className="mb-2">
|
||||
<Controller
|
||||
name={`ocoType`}
|
||||
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 { onChange, value } = field;
|
||||
render={({ field, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<RadioGroup
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Radio
|
||||
value={Schema.OrderType.TYPE_MARKET}
|
||||
id={`ocoTypeMarket`}
|
||||
label={'Market'}
|
||||
<div className="mb-2">
|
||||
<TradingInput
|
||||
data-testid="triggerPrice"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
appendElement={asset.symbol}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
<Radio
|
||||
value={Schema.OrderType.TYPE_LIMIT}
|
||||
id={`ocoTypeLimit`}
|
||||
label={'Limit'}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<Trigger
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
assetSymbol={asset.symbol}
|
||||
marketPrice={marketPrice}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
oco
|
||||
/>
|
||||
<hr className="mb-2 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<Price
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
quoteName={quoteName}
|
||||
oco
|
||||
/>
|
||||
<Size control={control} sizeStep={sizeStep} oco />
|
||||
<TimeInForce control={control} oco />
|
||||
<div className="flex gap-2 mb-2 justify-end">
|
||||
<ReduceOnly />
|
||||
{errors.triggerPrice && (
|
||||
<TradingInputError testId="stop-order-error-message-trigger-price">
|
||||
{errors.triggerPrice.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</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, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<TradingInput
|
||||
type="number"
|
||||
step={trailingPercentOffsetStep}
|
||||
appendElement="%"
|
||||
data-testid="triggerTrailingPercentOffset"
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{errors.triggerTrailingPercentOffset && (
|
||||
<TradingInputError testId="stop-order-error-message-trigger-trailing-percent-offset">
|
||||
{errors.triggerTrailingPercentOffset.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Controller
|
||||
name="triggerType"
|
||||
control={control}
|
||||
rules={{ deps: ['triggerTrailingPercentOffset', 'triggerPrice'] }}
|
||||
render={({ field }) => {
|
||||
const { onChange, value } = field;
|
||||
return (
|
||||
<TradingRadioGroup
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
>
|
||||
<TradingRadio
|
||||
value="price"
|
||||
id="triggerType-price"
|
||||
label={'Price'}
|
||||
/>
|
||||
<TradingRadio
|
||||
value="trailingPercentOffset"
|
||||
id="triggerType-trailingPercentOffset"
|
||||
label={'Trailing Percent Offset'}
|
||||
/>
|
||||
</TradingRadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
<div className="mb-2">
|
||||
<div className="flex items-start gap-4">
|
||||
<TradingFormGroup
|
||||
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, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<TradingInput
|
||||
id="order-size"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
data-testid="order-size"
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
<div className="pt-5 leading-10">@</div>
|
||||
<div className="flex-1">
|
||||
{type === Schema.OrderType.TYPE_LIMIT ? (
|
||||
<TradingFormGroup
|
||||
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, fieldState }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<TradingInput
|
||||
id="input-price-quote"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
data-testid="order-price"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
) : (
|
||||
<div
|
||||
className="text-sm text-right pt-5 leading-10"
|
||||
data-testid="price"
|
||||
>
|
||||
{priceFormatted && quoteName
|
||||
? `~${priceFormatted} ${quoteName}`
|
||||
: '-'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{errors.size && (
|
||||
<TradingInputError testId="stop-order-error-message-size">
|
||||
{errors.size.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
|
||||
{!errors.size &&
|
||||
errors.price &&
|
||||
type === Schema.OrderType.TYPE_LIMIT && (
|
||||
<TradingInputError testId="stop-order-error-message-price">
|
||||
{errors.price.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<TradingFormGroup
|
||||
label={t('Time in force')}
|
||||
labelFor="select-time-in-force"
|
||||
compact={true}
|
||||
>
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<TradingSelect
|
||||
id="select-time-in-force"
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
hasError={!!fieldState.error}
|
||||
{...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>
|
||||
</TradingSelect>
|
||||
)}
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
{errors.timeInForce && (
|
||||
<TradingInputError testId="stop-error-message-tif">
|
||||
{errors.timeInForce.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</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={(value) => {
|
||||
if (
|
||||
value &&
|
||||
(!expiresAt || new Date(expiresAt).getTime() < Date.now())
|
||||
) {
|
||||
setValue('expiresAt', formatForInput(new Date()), {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
onCheckedChange(value);
|
||||
}}
|
||||
<TradingCheckbox
|
||||
onCheckedChange={onCheckedChange}
|
||||
checked={value}
|
||||
name="expire"
|
||||
label={t('Expire')}
|
||||
@@ -744,47 +503,54 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<TradingCheckbox
|
||||
name="reduce-only"
|
||||
checked={true}
|
||||
disabled={true}
|
||||
label={
|
||||
<Tooltip description={<span>{t(REDUCE_ONLY_TOOLTIP)}</span>}>
|
||||
<>{t('Reduce only')}</>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{expire && (
|
||||
<>
|
||||
<FormGroup label={t('Strategy')} labelFor="expiryStrategy">
|
||||
<TradingFormGroup
|
||||
label={t('Strategy')}
|
||||
labelFor="expiryStrategy"
|
||||
compact={true}
|
||||
>
|
||||
<Controller
|
||||
name="expiryStrategy"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const { onChange, value } = field;
|
||||
return (
|
||||
<RadioGroup
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Radio
|
||||
disabled={oco}
|
||||
<TradingRadioGroup orientation="horizontal" {...field}>
|
||||
<TradingRadio
|
||||
value={
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||
}
|
||||
id="expiryStrategy-submit"
|
||||
label={'Submit'}
|
||||
/>
|
||||
<Radio
|
||||
<TradingRadio
|
||||
value={
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS
|
||||
}
|
||||
id="expiryStrategy-cancel"
|
||||
label={'Cancel'}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</TradingRadioGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<div className="mb-4">
|
||||
</TradingFormGroup>
|
||||
<div className="mb-2">
|
||||
<Controller
|
||||
name="expiresAt"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need provide a expiry time/date'),
|
||||
validate: validateExpiration,
|
||||
}}
|
||||
render={({ field }) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DealTicket } from './deal-ticket';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import type { OrdersQuery } from '@vegaprotocol/orders';
|
||||
import {
|
||||
DealTicketType,
|
||||
@@ -134,6 +135,20 @@ describe('DealTicket', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should display last price for market type order', () => {
|
||||
render(generateJsx());
|
||||
act(() => {
|
||||
screen.getByTestId('order-type-Market').click();
|
||||
});
|
||||
// Assert last price is shown
|
||||
expect(screen.getByTestId('last-price')).toHaveTextContent(
|
||||
// eslint-disable-next-line
|
||||
`~${addDecimal(marketPrice, market.decimalPlaces)} ${
|
||||
market.tradableInstrument.instrument.product.quoteName
|
||||
}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should use local storage state for initial values', () => {
|
||||
const expectedOrder = {
|
||||
marketId: market.id,
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import type { FormEventHandler } from 'react';
|
||||
import { memo, useCallback, useEffect, useRef, useMemo } from 'react';
|
||||
import { Controller, useController, useForm } from 'react-hook-form';
|
||||
import { DealTicketAmount } from './deal-ticket-amount';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import {
|
||||
DealTicketFeeDetails,
|
||||
@@ -16,10 +17,8 @@ import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { mapFormValuesToOrderSubmission } from '../../utils/map-form-values-to-submission';
|
||||
import {
|
||||
TradingInput as Input,
|
||||
TradingCheckbox as Checkbox,
|
||||
TradingFormGroup as FormGroup,
|
||||
TradingInputError as InputError,
|
||||
TradingCheckbox,
|
||||
TradingInputError,
|
||||
Intent,
|
||||
Notification,
|
||||
Tooltip,
|
||||
@@ -29,13 +28,7 @@ import {
|
||||
useEstimatePositionQuery,
|
||||
useOpenVolume,
|
||||
} from '@vegaprotocol/positions';
|
||||
import {
|
||||
toBigNum,
|
||||
removeDecimal,
|
||||
validateAmount,
|
||||
toDecimal,
|
||||
formatForInput,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { toBigNum, removeDecimal } from '@vegaprotocol/utils';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||
import type { OrderInfo } from '@vegaprotocol/types';
|
||||
@@ -175,7 +168,6 @@ export const DealTicket = ({
|
||||
const rawPrice = watch('price');
|
||||
const iceberg = watch('iceberg');
|
||||
const peakSize = watch('peakSize');
|
||||
const expiresAt = watch('expiresAt');
|
||||
|
||||
useEffect(() => {
|
||||
const size = storedFormValues?.[dealTicketType]?.size;
|
||||
@@ -340,10 +332,6 @@ export const DealTicket = ({
|
||||
},
|
||||
});
|
||||
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={
|
||||
@@ -378,82 +366,15 @@ export const DealTicket = ({
|
||||
<SideSelector value={field.value} onValueChange={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="size"
|
||||
<DealTicketAmount
|
||||
type={type}
|
||||
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, fieldState }) => (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
label={t('Size')}
|
||||
labelFor="input-order-size-limit"
|
||||
compact
|
||||
>
|
||||
<Input
|
||||
id="input-order-size-limit"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
data-testid="order-size"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError testId="deal-ticket-error-message-size">
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
market={market}
|
||||
marketData={marketData}
|
||||
marketPrice={marketPrice || undefined}
|
||||
sizeError={errors.size?.message}
|
||||
priceError={errors.price?.message}
|
||||
/>
|
||||
{type === Schema.OrderType.TYPE_LIMIT && (
|
||||
<Controller
|
||||
name="price"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need provide a price'),
|
||||
min: {
|
||||
value: priceStep,
|
||||
message: t('Price cannot be lower than ' + priceStep),
|
||||
},
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
labelFor="input-price-quote"
|
||||
label={t(`Price (${quoteName})`)}
|
||||
compact
|
||||
>
|
||||
<Input
|
||||
id="input-price-quote"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
data-testid="order-price"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
{fieldState.error && (
|
||||
<InputError testId="deal-ticket-error-message-price">
|
||||
{fieldState.error.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
control={control}
|
||||
@@ -467,17 +388,7 @@ export const DealTicket = ({
|
||||
<TimeInForceSelector
|
||||
value={field.value}
|
||||
orderType={type}
|
||||
onSelect={(value) => {
|
||||
if (
|
||||
value === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT &&
|
||||
(!expiresAt || new Date(expiresAt).getTime() < Date.now())
|
||||
) {
|
||||
setValue('expiresAt', formatForInput(new Date()), {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
field.onChange(value);
|
||||
}}
|
||||
onSelect={field.onChange}
|
||||
market={market}
|
||||
marketData={marketData}
|
||||
errorMessage={errors.timeInForce?.message}
|
||||
@@ -490,7 +401,6 @@ export const DealTicket = ({
|
||||
name="expiresAt"
|
||||
control={control}
|
||||
rules={{
|
||||
required: t('You need provide a expiry time/date'),
|
||||
validate: validateExpiration,
|
||||
}}
|
||||
render={({ field }) => (
|
||||
@@ -507,7 +417,7 @@ export const DealTicket = ({
|
||||
name="postOnly"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
<TradingCheckbox
|
||||
name="post-only"
|
||||
checked={!disablePostOnlyCheckbox && field.value}
|
||||
disabled={disablePostOnlyCheckbox}
|
||||
@@ -539,7 +449,7 @@ export const DealTicket = ({
|
||||
name="reduceOnly"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
<TradingCheckbox
|
||||
name="reduce-only"
|
||||
checked={!disableReduceOnlyCheckbox && field.value}
|
||||
disabled={disableReduceOnlyCheckbox}
|
||||
@@ -573,7 +483,7 @@ export const DealTicket = ({
|
||||
name="iceberg"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
<TradingCheckbox
|
||||
name="iceberg"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
@@ -662,11 +572,11 @@ export const NoWalletWarning = ({
|
||||
if (isReadOnly) {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<InputError testId="deal-ticket-error-message-summary">
|
||||
<TradingInputError testId="deal-ticket-error-message-summary">
|
||||
{
|
||||
'You need to connect your own wallet to start trading on this market'
|
||||
}
|
||||
</InputError>
|
||||
</TradingInputError>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -703,9 +613,9 @@ const SummaryMessage = memo(
|
||||
if (error?.message) {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<InputError testId="deal-ticket-error-message-summary">
|
||||
<TradingInputError testId="deal-ticket-error-message-summary">
|
||||
{error?.message}
|
||||
</InputError>
|
||||
</TradingInputError>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,28 +18,30 @@ export const ExpirySelector = ({
|
||||
onSelect,
|
||||
errorMessage,
|
||||
}: ExpirySelectorProps) => {
|
||||
const now = useRef(new Date());
|
||||
const date = value ? new Date(value) : now.current;
|
||||
const dateFormatted = formatForInput(date);
|
||||
const minDate = formatForInput(date);
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<TradingFormGroup
|
||||
label={t('Expiry time/date')}
|
||||
labelFor="expiration"
|
||||
compact
|
||||
>
|
||||
<TradingInput
|
||||
data-testid="date-picker-field"
|
||||
id="expiration"
|
||||
type="datetime-local"
|
||||
value={value && formatForInput(new Date(value))}
|
||||
onChange={(e) => onSelect(e.target.value)}
|
||||
min={formatForInput(useRef(new Date()).current)}
|
||||
hasError={!!errorMessage}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<TradingInputError testId="deal-ticket-error-message-expiry">
|
||||
{errorMessage}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
</div>
|
||||
<TradingFormGroup
|
||||
label={t('Expiry time/date')}
|
||||
labelFor="expiration"
|
||||
compact={true}
|
||||
>
|
||||
<TradingInput
|
||||
data-testid="date-picker-field"
|
||||
id="expiration"
|
||||
type="datetime-local"
|
||||
value={dateFormatted}
|
||||
onChange={(e) => onSelect(e.target.value)}
|
||||
min={minDate}
|
||||
hasError={!!errorMessage}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<TradingInputError testId="deal-ticket-error-message-expiry">
|
||||
{errorMessage}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export * from './deal-ticket-amount';
|
||||
export * from './deal-ticket-container';
|
||||
export * from './deal-ticket-limit-amount';
|
||||
export * from './deal-ticket-market-amount';
|
||||
export * from './deal-ticket';
|
||||
export * from './deal-ticket-stop-order';
|
||||
export * from './deal-ticket-container';
|
||||
|
||||
@@ -90,34 +90,32 @@ export const TimeInForceSelector = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<TradingFormGroup
|
||||
label={t('Time in force')}
|
||||
labelFor="select-time-in-force"
|
||||
compact={true}
|
||||
<TradingFormGroup
|
||||
label={t('Time in force')}
|
||||
labelFor="select-time-in-force"
|
||||
compact={true}
|
||||
>
|
||||
<TradingSelect
|
||||
id="select-time-in-force"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onSelect(e.target.value as Schema.OrderTimeInForce);
|
||||
}}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
hasError={!!errorMessage}
|
||||
>
|
||||
<TradingSelect
|
||||
id="select-time-in-force"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onSelect(e.target.value as Schema.OrderTimeInForce);
|
||||
}}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
hasError={!!errorMessage}
|
||||
>
|
||||
{options.map(([key, value]) => (
|
||||
<option key={key} value={value}>
|
||||
{timeInForceLabel(value)}
|
||||
</option>
|
||||
))}
|
||||
</TradingSelect>
|
||||
{errorMessage && (
|
||||
<TradingInputError testId="deal-ticket-error-message-tif">
|
||||
{renderError(errorMessage)}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
</div>
|
||||
{options.map(([key, value]) => (
|
||||
<option key={key} value={value}>
|
||||
{timeInForceLabel(value)}
|
||||
</option>
|
||||
))}
|
||||
</TradingSelect>
|
||||
{errorMessage && (
|
||||
<TradingInputError testId="deal-ticket-error-message-tif">
|
||||
{renderError(errorMessage)}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -76,7 +76,7 @@ export const TypeToggle = ({
|
||||
<TradingDropdownTrigger
|
||||
data-testid="order-type-Stop"
|
||||
className={classNames(
|
||||
'rounded px-2 flex flex-nowrap items-center justify-center',
|
||||
'rounded px-3 flex flex-nowrap items-center justify-center',
|
||||
{
|
||||
'bg-vega-clight-500 dark:bg-vega-cdark-500': selectedOption,
|
||||
}
|
||||
|
||||
@@ -28,17 +28,6 @@ export interface StopOrderFormValues {
|
||||
expire: boolean;
|
||||
expiryStrategy?: Schema.StopOrderExpiryStrategy;
|
||||
expiresAt?: string;
|
||||
|
||||
oco?: boolean;
|
||||
|
||||
ocoTriggerType: 'price' | 'trailingPercentOffset';
|
||||
ocoTriggerPrice?: string;
|
||||
ocoTriggerTrailingPercentOffset?: string;
|
||||
|
||||
ocoType: OrderType;
|
||||
ocoSize: string;
|
||||
ocoTimeInForce: OrderTimeInForce;
|
||||
ocoPrice?: string;
|
||||
}
|
||||
|
||||
export type OrderFormValues = {
|
||||
@@ -149,7 +138,6 @@ export const useDealTicketFormValues = create<Store>()(
|
||||
})),
|
||||
{
|
||||
name: 'vega_deal_ticket_store',
|
||||
version: 1,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -59,22 +59,6 @@ export const mapFormValuesToOrderSubmission = (
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const setTrigger = (
|
||||
stopOrderSetup: StopOrderSetup,
|
||||
triggerType: StopOrderFormValues['triggerPrice'],
|
||||
triggerPrice: StopOrderFormValues['triggerPrice'],
|
||||
triggerTrailingPercentOffset: StopOrderFormValues['triggerTrailingPercentOffset'],
|
||||
decimalPlaces: number
|
||||
) => {
|
||||
if (triggerType === 'price') {
|
||||
stopOrderSetup.price = removeDecimal(triggerPrice ?? '', decimalPlaces);
|
||||
} else if (triggerType === 'trailingPercentOffset') {
|
||||
stopOrderSetup.trailingPercentOffset = (
|
||||
Number(triggerTrailingPercentOffset) / 100
|
||||
).toFixed(3);
|
||||
}
|
||||
};
|
||||
|
||||
export const mapFormValuesToStopOrdersSubmission = (
|
||||
data: StopOrderFormValues,
|
||||
marketId: string,
|
||||
@@ -97,46 +81,31 @@ export const mapFormValuesToStopOrdersSubmission = (
|
||||
positionDecimalPlaces
|
||||
),
|
||||
};
|
||||
setTrigger(
|
||||
stopOrderSetup,
|
||||
data.triggerType,
|
||||
data.triggerPrice,
|
||||
data.triggerTrailingPercentOffset,
|
||||
decimalPlaces
|
||||
);
|
||||
let oppositeStopOrderSetup: StopOrderSetup | undefined = undefined;
|
||||
if (data.oco) {
|
||||
oppositeStopOrderSetup = {
|
||||
orderSubmission: mapFormValuesToOrderSubmission(
|
||||
{
|
||||
type: data.ocoType,
|
||||
side: data.side,
|
||||
size: data.ocoSize,
|
||||
timeInForce: data.ocoTimeInForce,
|
||||
price: data.ocoPrice,
|
||||
reduceOnly: true,
|
||||
},
|
||||
marketId,
|
||||
decimalPlaces,
|
||||
positionDecimalPlaces
|
||||
),
|
||||
};
|
||||
setTrigger(
|
||||
oppositeStopOrderSetup,
|
||||
data.ocoTriggerType,
|
||||
data.ocoTriggerPrice,
|
||||
data.ocoTriggerTrailingPercentOffset,
|
||||
if (data.triggerType === 'price') {
|
||||
stopOrderSetup.price = removeDecimal(
|
||||
data.triggerPrice ?? '',
|
||||
decimalPlaces
|
||||
);
|
||||
} else if (data.triggerType === 'trailingPercentOffset') {
|
||||
stopOrderSetup.trailingPercentOffset = (
|
||||
Number(data.triggerTrailingPercentOffset) / 100
|
||||
).toFixed(3);
|
||||
}
|
||||
|
||||
if (data.expire) {
|
||||
const expiresAt = data.expiresAt && toNanoSeconds(data.expiresAt);
|
||||
stopOrderSetup.expiresAt = expiresAt;
|
||||
stopOrderSetup.expiryStrategy = data.expiryStrategy;
|
||||
if (oppositeStopOrderSetup) {
|
||||
oppositeStopOrderSetup.expiresAt = expiresAt;
|
||||
oppositeStopOrderSetup.expiryStrategy = data.expiryStrategy;
|
||||
stopOrderSetup.expiresAt = data.expiresAt && toNanoSeconds(data.expiresAt);
|
||||
if (
|
||||
data.expiryStrategy ===
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS
|
||||
) {
|
||||
stopOrderSetup.expiryStrategy =
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS;
|
||||
} else if (
|
||||
data.expiryStrategy ===
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||
) {
|
||||
stopOrderSetup.expiryStrategy =
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,14 +114,12 @@ export const mapFormValuesToStopOrdersSubmission = (
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
) {
|
||||
submission.risesAbove = stopOrderSetup;
|
||||
submission.fallsBelow = oppositeStopOrderSetup;
|
||||
}
|
||||
if (
|
||||
data.triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
) {
|
||||
submission.fallsBelow = stopOrderSetup;
|
||||
submission.risesAbove = oppositeStopOrderSetup;
|
||||
}
|
||||
|
||||
return submission;
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumberPercentage,
|
||||
isNumeric,
|
||||
priceChange,
|
||||
priceChangePercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { PriceChangeCell, signedNumberCssClass } from '@vegaprotocol/datagrid';
|
||||
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { isNumeric } from '@vegaprotocol/utils';
|
||||
import { PriceChangeCell } from '@vegaprotocol/datagrid';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useCandles } from '../../hooks/use-candles';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
marketId?: string;
|
||||
@@ -55,39 +47,10 @@ export const Last24hPriceChange = ({
|
||||
if (error || !isNumeric(decimalPlaces)) {
|
||||
return <span>-</span>;
|
||||
}
|
||||
|
||||
const candles = oneDayCandles?.map((c) => c.close) || initialValue || [];
|
||||
const change = priceChange(candles);
|
||||
const changePercentage = priceChangePercentage(candles);
|
||||
|
||||
return (
|
||||
<span
|
||||
className={classNames(
|
||||
'flex items-center gap-1',
|
||||
signedNumberCssClass(change)
|
||||
)}
|
||||
>
|
||||
<Arrow value={change} />
|
||||
<span data-testid="price-change-percentage">
|
||||
{formatNumberPercentage(new BigNumber(changePercentage.toString()), 2)}
|
||||
</span>
|
||||
<span data-testid="price-change">
|
||||
{addDecimalsFormatNumber(change.toString(), decimalPlaces ?? 0, 3)}
|
||||
</span>
|
||||
</span>
|
||||
<PriceChangeCell
|
||||
candles={oneDayCandles?.map((c) => c.close) || initialValue || []}
|
||||
decimalPlaces={decimalPlaces}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Arrow = ({ value }: { value: number | bigint }) => {
|
||||
const size = 10;
|
||||
|
||||
if (value > 0) {
|
||||
return <VegaIcon name={VegaIconNames.ARROW_UP} size={size} />;
|
||||
}
|
||||
|
||||
if (value < 0) {
|
||||
return <VegaIcon name={VegaIconNames.ARROW_DOWN} size={size} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -98,7 +98,7 @@ export const Row = ({
|
||||
<div style={{ wordBreak: 'break-word' }}>
|
||||
{valueDiffersFromParentMarket ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="line-through dark:text-vega-dark-300">
|
||||
<span className="line-through">
|
||||
{getFormattedValue(parentValue)}
|
||||
</span>
|
||||
<span>{formattedValue}</span>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Fragment, useMemo, useState } from 'react';
|
||||
import { Fragment, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { marketDataProvider } from '../../market-data-provider';
|
||||
import { totalFeesPercentage } from '../../market-utils';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
Lozenge,
|
||||
Splash,
|
||||
Tooltip,
|
||||
VegaIcon,
|
||||
@@ -28,15 +27,9 @@ import type {
|
||||
} from './market-info-data-provider';
|
||||
import { Last24hVolume } from '../last-24h-volume';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type {
|
||||
DataSourceDefinition,
|
||||
MarketTradingMode,
|
||||
SignerKind,
|
||||
} from '@vegaprotocol/types';
|
||||
import {
|
||||
ConditionOperatorMapping,
|
||||
MarketTradingModeMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import type { DataSourceDefinition, SignerKind } from '@vegaprotocol/types';
|
||||
import { ConditionOperatorMapping } from '@vegaprotocol/types';
|
||||
import { MarketTradingModeMapping } from '@vegaprotocol/types';
|
||||
import {
|
||||
DApp,
|
||||
FLAGS,
|
||||
@@ -55,6 +48,8 @@ import {
|
||||
useSuccessorMarketQuery,
|
||||
} from '../../__generated__';
|
||||
import { useSuccessorMarketProposalDetailsQuery } from '@vegaprotocol/proposals';
|
||||
import type { MarketTradingMode } from '@vegaprotocol/types';
|
||||
import type { Signer } from '@vegaprotocol/types';
|
||||
import classNames from 'classnames';
|
||||
import compact from 'lodash/compact';
|
||||
|
||||
@@ -580,6 +575,7 @@ export const RiskFactorsInfoPanel = ({
|
||||
export const PriceMonitoringBoundsInfoPanel = ({
|
||||
market,
|
||||
triggerIndex,
|
||||
parentMarket,
|
||||
}: MarketInfoProps & {
|
||||
triggerIndex: number;
|
||||
}) => {
|
||||
@@ -588,13 +584,33 @@ export const PriceMonitoringBoundsInfoPanel = ({
|
||||
variables: { marketId: market.id },
|
||||
});
|
||||
|
||||
const { data: parentData } = useDataProvider({
|
||||
dataProvider: marketDataProvider,
|
||||
variables: { marketId: parentMarket?.id || '' },
|
||||
skip:
|
||||
!parentMarket ||
|
||||
!parentMarket?.priceMonitoringSettings?.parameters?.triggers?.[
|
||||
triggerIndex
|
||||
],
|
||||
});
|
||||
|
||||
const quoteUnit =
|
||||
market?.tradableInstrument.instrument.product?.quoteName || '';
|
||||
const parentQuoteUnit =
|
||||
parentMarket?.tradableInstrument.instrument.product?.quoteName || '';
|
||||
const isParentQuoteUnitEqual = quoteUnit === parentQuoteUnit;
|
||||
|
||||
const trigger =
|
||||
market.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
|
||||
const parentTrigger =
|
||||
parentMarket?.priceMonitoringSettings?.parameters?.triggers?.[triggerIndex];
|
||||
const isParentTriggerEqual = isEqual(trigger, parentTrigger);
|
||||
|
||||
const bounds = data?.priceMonitoringBounds?.[triggerIndex];
|
||||
const parentBounds = parentData?.priceMonitoringBounds?.[triggerIndex];
|
||||
|
||||
const shouldShowParentData =
|
||||
isParentQuoteUnitEqual && isParentTriggerEqual && !!parentBounds;
|
||||
|
||||
if (!trigger) {
|
||||
console.error(
|
||||
@@ -622,6 +638,14 @@ export const PriceMonitoringBoundsInfoPanel = ({
|
||||
highestPrice: bounds.maxValidPrice,
|
||||
lowestPrice: bounds.minValidPrice,
|
||||
}}
|
||||
parentData={
|
||||
shouldShowParentData
|
||||
? {
|
||||
highestPrice: parentBounds.maxValidPrice,
|
||||
lowestPrice: parentBounds.minValidPrice,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
assetSymbol={quoteUnit}
|
||||
/>
|
||||
@@ -815,76 +839,45 @@ export const OracleInfoPanel = ({
|
||||
: (parentProduct?.dataSourceSpecForTradingTermination
|
||||
?.data as DataSourceDefinition);
|
||||
|
||||
const shouldShowParentData =
|
||||
parentMarket !== undefined &&
|
||||
const isParentDataSourceSpecEqual =
|
||||
parentDataSourceSpec !== undefined &&
|
||||
dataSourceSpec === parentDataSourceSpec;
|
||||
const isParentDataSourceSpecIdEqual =
|
||||
parentDataSourceSpecId !== undefined &&
|
||||
!isEqual(dataSourceSpec, parentDataSourceSpec);
|
||||
|
||||
const wrapperClasses = classNames('mb-4', {
|
||||
'flex items-center gap-6': shouldShowParentData,
|
||||
});
|
||||
dataSourceSpecId === parentDataSourceSpecId;
|
||||
|
||||
// We'll only provide successor parent data (if it differs) to the
|
||||
// DataSourceProof component. Having an old external link struck through
|
||||
// is unlikely to be useful.
|
||||
return (
|
||||
<>
|
||||
{shouldShowParentData && (
|
||||
<Lozenge variant={Intent.Primary} className="text-sm">
|
||||
{t('Updated')}
|
||||
</Lozenge>
|
||||
)}
|
||||
<div className="flex flex-col gap-2">
|
||||
<DataSourceProof
|
||||
data-testid="oracle-proof-links"
|
||||
data={dataSourceSpec}
|
||||
providers={data}
|
||||
type={type}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
parentData={
|
||||
isParentDataSourceSpecEqual ? undefined : parentDataSourceSpec
|
||||
}
|
||||
parentDataSourceSpecId={
|
||||
isParentDataSourceSpecIdEqual ? undefined : parentDataSourceSpecId
|
||||
}
|
||||
/>
|
||||
|
||||
<div className={wrapperClasses}>
|
||||
{shouldShowParentData &&
|
||||
parentDataSourceSpec &&
|
||||
parentDataSourceSpecId &&
|
||||
parentProduct && (
|
||||
<div className="flex flex-col gap-2 text-vega-dark-300 line-through">
|
||||
<DataSourceProof
|
||||
data-testid="oracle-proof-links"
|
||||
data={parentDataSourceSpec}
|
||||
providers={data}
|
||||
type={type}
|
||||
dataSourceSpecId={parentDataSourceSpecId}
|
||||
/>
|
||||
|
||||
<ExternalLink
|
||||
data-testid="oracle-spec-links"
|
||||
href={`${VEGA_EXPLORER_URL}/oracles/${
|
||||
type === 'settlementData'
|
||||
? parentProduct.dataSourceSpecForSettlementData.id
|
||||
: parentProduct.dataSourceSpecForTradingTermination.id
|
||||
}`}
|
||||
>
|
||||
{type === 'settlementData'
|
||||
? t('View settlement data specification')
|
||||
: t('View termination specification')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<DataSourceProof
|
||||
data-testid="oracle-proof-links"
|
||||
data={dataSourceSpec}
|
||||
providers={data}
|
||||
type={type}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
/>
|
||||
|
||||
<ExternalLink
|
||||
data-testid="oracle-spec-links"
|
||||
href={`${VEGA_EXPLORER_URL}/oracles/${
|
||||
type === 'settlementData'
|
||||
? product.dataSourceSpecForSettlementData.id
|
||||
: product.dataSourceSpecForTradingTermination.id
|
||||
}`}
|
||||
>
|
||||
{type === 'settlementData'
|
||||
? t('View settlement data specification')
|
||||
: t('View termination specification')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<ExternalLink
|
||||
data-testid="oracle-spec-links"
|
||||
href={`${VEGA_EXPLORER_URL}/oracles/${
|
||||
type === 'settlementData'
|
||||
? product.dataSourceSpecForSettlementData.id
|
||||
: product.dataSourceSpecForTradingTermination.id
|
||||
}`}
|
||||
>
|
||||
{type === 'settlementData'
|
||||
? t('View settlement data specification')
|
||||
: t('View termination specification')}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -893,14 +886,28 @@ export const DataSourceProof = ({
|
||||
providers,
|
||||
type,
|
||||
dataSourceSpecId,
|
||||
parentData,
|
||||
parentDataSourceSpecId,
|
||||
}: {
|
||||
data: DataSourceDefinition;
|
||||
providers: Provider[] | undefined;
|
||||
type: 'settlementData' | 'termination';
|
||||
dataSourceSpecId: string;
|
||||
parentData?: DataSourceDefinition;
|
||||
parentDataSourceSpecId?: string;
|
||||
}) => {
|
||||
// If this is a successor market, we'll only pass parent data to child
|
||||
// components for comparison if the data differs from the parent market.
|
||||
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
|
||||
const signers = data.sourceType.sourceType.signers || [];
|
||||
let parentSigners: Signer[];
|
||||
|
||||
if (
|
||||
parentData &&
|
||||
parentData.sourceType.__typename === 'DataSourceDefinitionExternal'
|
||||
) {
|
||||
parentSigners = parentData.sourceType.sourceType?.signers || [];
|
||||
}
|
||||
|
||||
if (!providers?.length) {
|
||||
return <NoOracleProof type={type} />;
|
||||
@@ -908,15 +915,34 @@ export const DataSourceProof = ({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{signers.map(({ signer }, i) => (
|
||||
<OracleLink
|
||||
key={i}
|
||||
providers={providers}
|
||||
signer={signer}
|
||||
type={type}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
/>
|
||||
))}
|
||||
{signers.map(({ signer }, i) => {
|
||||
const parentSigner = parentSigners?.find(
|
||||
({ signer: ParentSigner }) =>
|
||||
ParentSigner.__typename === signer.__typename
|
||||
)?.signer;
|
||||
|
||||
const isParentSignerEqual = isEqual(signer, parentSigner);
|
||||
|
||||
return isParentSignerEqual ? (
|
||||
<OracleLink
|
||||
key={i}
|
||||
providers={providers}
|
||||
signer={signer}
|
||||
type={type}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
/>
|
||||
) : (
|
||||
<OracleLink
|
||||
key={i}
|
||||
providers={providers}
|
||||
signer={signer}
|
||||
type={type}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
parentSigner={parentSigner}
|
||||
parentDataSourceSpecId={parentDataSourceSpecId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -976,13 +1002,22 @@ const OracleLink = ({
|
||||
signer,
|
||||
type,
|
||||
dataSourceSpecId,
|
||||
parentSigner,
|
||||
parentDataSourceSpecId,
|
||||
}: {
|
||||
providers: Provider[];
|
||||
signer: SignerKind;
|
||||
type: 'settlementData' | 'termination';
|
||||
dataSourceSpecId: string;
|
||||
parentSigner?: SignerKind;
|
||||
parentDataSourceSpecId?: string;
|
||||
}) => {
|
||||
// If this is a successor market, the parent market data will only have been passed
|
||||
// in if it differs from the current data.
|
||||
const signerProviders = getSignerProviders(signer, providers);
|
||||
const parentSignerProviders = parentSigner
|
||||
? getSignerProviders(parentSigner, providers)
|
||||
: [];
|
||||
|
||||
if (!signerProviders.length) {
|
||||
return <NoOracleProof type={type} />;
|
||||
@@ -990,13 +1025,34 @@ const OracleLink = ({
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
{signerProviders.map((provider) => (
|
||||
<OracleProfile
|
||||
key={dataSourceSpecId}
|
||||
provider={provider}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
/>
|
||||
))}
|
||||
{signerProviders.map((provider) => {
|
||||
// Making the assumption here that if the provider name is the same,
|
||||
// that it is the same provider that the parent market used.
|
||||
const parentProvider = parentSignerProviders.find(
|
||||
(p) => p.name === provider.name
|
||||
);
|
||||
|
||||
const isParentProviderEqual =
|
||||
parentProvider !== undefined && isEqual(provider, parentProvider);
|
||||
|
||||
// We only want to pass the parent data to the child component if the
|
||||
// data differs from the parent market.
|
||||
return isParentProviderEqual ? (
|
||||
<OracleProfile
|
||||
key={dataSourceSpecId}
|
||||
provider={provider}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
/>
|
||||
) : (
|
||||
<OracleProfile
|
||||
key={dataSourceSpecId}
|
||||
provider={provider}
|
||||
dataSourceSpecId={dataSourceSpecId}
|
||||
parentProvider={parentProvider}
|
||||
parentDataSourceSpecId={parentDataSourceSpecId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1019,13 +1075,18 @@ const NoOracleProof = ({
|
||||
const OracleProfile = (props: {
|
||||
provider: Provider;
|
||||
dataSourceSpecId: string;
|
||||
parentProvider?: Provider;
|
||||
parentDataSourceSpecId?: string;
|
||||
}) => {
|
||||
// If this is a successor market, the parent market data will only have been passed
|
||||
// in if it differs from the current data.
|
||||
const [open, onChange] = useState(false);
|
||||
return (
|
||||
<div key={props.provider.name}>
|
||||
<OracleBasicProfile
|
||||
provider={props.provider}
|
||||
onClick={() => onChange(!open)}
|
||||
parentProvider={props.parentProvider}
|
||||
/>
|
||||
<OracleDialog {...props} open={open} onChange={onChange} />
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ExternalLink,
|
||||
Icon,
|
||||
Intent,
|
||||
Lozenge,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
@@ -59,10 +60,12 @@ export const OracleBasicProfile = ({
|
||||
provider,
|
||||
onClick,
|
||||
markets: oracleMarkets,
|
||||
parentProvider,
|
||||
}: {
|
||||
provider: Provider;
|
||||
markets?: OracleMarketSpecFieldsFragment[] | undefined;
|
||||
onClick?: (value?: boolean) => void;
|
||||
parentProvider?: Provider;
|
||||
}) => {
|
||||
const { icon, message, intent } = getVerifiedStatusIcon(provider);
|
||||
|
||||
@@ -78,8 +81,14 @@ export const OracleBasicProfile = ({
|
||||
icon: getLinkIcon(proof.type),
|
||||
}));
|
||||
|
||||
// If this is a successor market and there's a different parent provider,
|
||||
// we'll just show that there's been a change, rather than add old data
|
||||
// in alongside the new provider.
|
||||
return (
|
||||
<>
|
||||
{parentProvider && (
|
||||
<Lozenge variant={Intent.Primary}>{t('Updated')}</Lozenge>
|
||||
)}
|
||||
<span className="flex gap-1">
|
||||
{provider.url && (
|
||||
<span className="flex align-items-bottom text-md gap-1">
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { Market, MarketMaybeWithDataAndCandles } from './markets-provider';
|
||||
import {
|
||||
calcTradedFactor,
|
||||
filterAndSortMarkets,
|
||||
totalFeesPercentage,
|
||||
} from './market-utils';
|
||||
import type { Market } from './markets-provider';
|
||||
import { filterAndSortMarkets, totalFeesPercentage } from './market-utils';
|
||||
const { MarketState, MarketTradingMode } = Schema;
|
||||
|
||||
const MARKET_A: Partial<Market> = {
|
||||
@@ -81,52 +77,3 @@ describe('totalFees', () => {
|
||||
expect(totalFeesPercentage(i)).toEqual(o);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calcTradedFactor', () => {
|
||||
const marketA = {
|
||||
data: {
|
||||
markPrice: '10',
|
||||
},
|
||||
candles: [
|
||||
{
|
||||
volume: '1000',
|
||||
},
|
||||
],
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
settlementAsset: {
|
||||
decimals: 18,
|
||||
quantum: '1000000000000000000', // 1
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const marketB = {
|
||||
data: {
|
||||
markPrice: '10',
|
||||
},
|
||||
candles: [
|
||||
{
|
||||
volume: '1000',
|
||||
},
|
||||
],
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
settlementAsset: {
|
||||
decimals: 18,
|
||||
quantum: '1', // 0.0000000000000000001
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
it('a is "traded" more than b', () => {
|
||||
const fa = calcTradedFactor(marketA as MarketMaybeWithDataAndCandles);
|
||||
const fb = calcTradedFactor(marketB as MarketMaybeWithDataAndCandles);
|
||||
// it should be true because market a's asset is "more valuable" than b's
|
||||
expect(fa > fb).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { formatNumberPercentage, toBigNum } from '@vegaprotocol/utils';
|
||||
import { formatNumberPercentage } from '@vegaprotocol/utils';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import type {
|
||||
Market,
|
||||
Candle,
|
||||
MarketMaybeWithData,
|
||||
MarketMaybeWithDataAndCandles,
|
||||
} from '../';
|
||||
import type { Market, Candle, MarketMaybeWithData } from '../';
|
||||
const { MarketState, MarketTradingMode } = Schema;
|
||||
|
||||
export const totalFees = (fees: Market['fees']['factors']) => {
|
||||
@@ -91,18 +86,3 @@ export const calcCandleHigh = (candles: Candle[]): string | undefined => {
|
||||
export const calcCandleVolume = (candles: Candle[]): string | undefined =>
|
||||
candles &&
|
||||
candles.reduce((acc, c) => new BigNumber(acc).plus(c.volume).toString(), '0');
|
||||
|
||||
export const calcTradedFactor = (m: MarketMaybeWithDataAndCandles) => {
|
||||
const volume = Number(calcCandleVolume(m.candles || []) || 0);
|
||||
const price = m.data?.markPrice ? Number(m.data.markPrice) : 0;
|
||||
const quantum = Number(
|
||||
m.tradableInstrument.instrument.product.settlementAsset.quantum
|
||||
);
|
||||
const decimals = Number(
|
||||
m.tradableInstrument.instrument.product.settlementAsset.decimals
|
||||
);
|
||||
const fp = toBigNum(price, decimals);
|
||||
const fq = toBigNum(quantum, decimals);
|
||||
const factor = fq.multipliedBy(fp).multipliedBy(volume);
|
||||
return factor.toNumber();
|
||||
};
|
||||
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
VegaIconNames,
|
||||
DropdownMenuItem,
|
||||
TradingDropdownCopyItem,
|
||||
Pill,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
@@ -32,6 +32,7 @@ import type {
|
||||
VegaValueFormatterParams,
|
||||
VegaValueGetterParams,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { StopOrder } from '../order-data-provider/stop-orders-data-provider';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import type { Order } from '../order-data-provider';
|
||||
@@ -49,249 +50,236 @@ export type StopOrdersTableProps = TypedDataAgGrid<StopOrder> & {
|
||||
isReadOnly: boolean;
|
||||
};
|
||||
|
||||
export const StopOrdersTable = memo(
|
||||
({ onCancel, onMarketClick, onView, ...props }: StopOrdersTableProps) => {
|
||||
const showAllActions = !props.isReadOnly;
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
export const StopOrdersTable = memo<
|
||||
StopOrdersTableProps & { ref?: ForwardedRef<AgGridReact> }
|
||||
>(({ onCancel, onView, onMarketClick, ...props }: StopOrdersTableProps) => {
|
||||
const showAllActions = !props.isReadOnly;
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
},
|
||||
{
|
||||
headerName: t('Trigger'),
|
||||
field: 'trigger',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
sortable: false,
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'trigger'>): string =>
|
||||
data ? formatTrigger(data, data.market.decimalPlaces) : '',
|
||||
},
|
||||
{
|
||||
field: 'expiresAt',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'expiresAt'>) => {
|
||||
if (
|
||||
data &&
|
||||
value &&
|
||||
data?.expiryStrategy !==
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_UNSPECIFIED
|
||||
) {
|
||||
const expiresAt = getDateTimeFormat().format(new Date(value));
|
||||
const expiryStrategy =
|
||||
data.expiryStrategy ===
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||
? t('Submit')
|
||||
: t('Cancels');
|
||||
return `${expiryStrategy} ${expiresAt}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
{
|
||||
headerName: t('Trigger'),
|
||||
field: 'trigger',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
sortable: false,
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'trigger'>): string =>
|
||||
data ? formatTrigger(data, data.market.decimalPlaces) : '',
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
field: 'submission.size',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_BUY,
|
||||
[negativeClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_SELL,
|
||||
},
|
||||
{
|
||||
field: 'expiresAt',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'expiresAt'>) => {
|
||||
if (
|
||||
data &&
|
||||
value &&
|
||||
data?.expiryStrategy !==
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_UNSPECIFIED
|
||||
) {
|
||||
const expiresAt = getDateTimeFormat().format(new Date(value));
|
||||
const expiryStrategy =
|
||||
data.expiryStrategy ===
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||
? t('Submit')
|
||||
: t('Cancels');
|
||||
return `${expiryStrategy} ${expiresAt}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
field: 'submission.size',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_BUY,
|
||||
[negativeClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_SELL,
|
||||
},
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) => {
|
||||
return data?.submission.size && data.market
|
||||
? toBigNum(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces ?? 0
|
||||
)
|
||||
.multipliedBy(
|
||||
data.submission.side === Schema.Side.SIDE_SELL ? -1 : 1
|
||||
)
|
||||
.toNumber()
|
||||
: undefined;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'size'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(data.submission.size)) {
|
||||
return '-';
|
||||
}
|
||||
const prefix = data
|
||||
? data.submission.side === Schema.Side.SIDE_BUY
|
||||
? '+'
|
||||
: '-'
|
||||
: '';
|
||||
return (
|
||||
prefix +
|
||||
addDecimalsFormatNumber(
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) => {
|
||||
return data?.submission.size && data.market
|
||||
? toBigNum(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces
|
||||
data.market.positionDecimalPlaces ?? 0
|
||||
)
|
||||
);
|
||||
},
|
||||
.multipliedBy(
|
||||
data.submission.side === Schema.Side.SIDE_SELL ? -1 : 1
|
||||
)
|
||||
.toNumber()
|
||||
: undefined;
|
||||
},
|
||||
{
|
||||
field: 'submission.type',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTypeMapping,
|
||||
},
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<StopOrder, 'submission.type'>) =>
|
||||
value ? Schema.OrderTypeMapping[value] : '',
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'size'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(data.submission.size)) {
|
||||
return '-';
|
||||
}
|
||||
const prefix = data
|
||||
? data.submission.side === Schema.Side.SIDE_BUY
|
||||
? '+'
|
||||
: '-'
|
||||
: '';
|
||||
return (
|
||||
prefix +
|
||||
addDecimalsFormatNumber(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces
|
||||
)
|
||||
);
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.StopOrderStatusMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'status'>) => {
|
||||
return value ? Schema.StopOrderStatusMapping[value] : '';
|
||||
},
|
||||
cellRenderer: ({
|
||||
valueFormatted,
|
||||
data,
|
||||
}: {
|
||||
valueFormatted: string;
|
||||
data: StopOrder;
|
||||
}) => (
|
||||
<>
|
||||
<span data-testid={`order-status-${data?.id}`}>
|
||||
{valueFormatted}
|
||||
</span>
|
||||
{data.ocoLinkId && (
|
||||
<Pill
|
||||
size="xxs"
|
||||
className="uppercase ml-0.5"
|
||||
title={t('One Cancels the Other')}
|
||||
>
|
||||
OCO
|
||||
</Pill>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'submission.type',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTypeMapping,
|
||||
},
|
||||
{
|
||||
field: 'submission.price',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.price'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data?.market ||
|
||||
data.submission.type === Schema.OrderType.TYPE_MARKET ||
|
||||
!isNumeric(value)
|
||||
) {
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
},
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<StopOrder, 'submission.type'>) =>
|
||||
value ? Schema.OrderTypeMapping[value] : '',
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.StopOrderStatusMapping,
|
||||
},
|
||||
{
|
||||
field: 'submission.timeInForce',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTimeInForceMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.timeInForce'>) => {
|
||||
return value ? Schema.OrderTimeInForceCode[value] : '';
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'status'>) => {
|
||||
return value ? Schema.StopOrderStatusMapping[value] : '';
|
||||
},
|
||||
{
|
||||
field: 'updatedAt',
|
||||
filter: DateRangeFilter,
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) =>
|
||||
data?.updatedAt || data?.createdAt,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<StopOrder, 'createdAt'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
const value = data.updatedAt || data.createdAt;
|
||||
return (
|
||||
<span data-value={value}>
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
cellRenderer: ({
|
||||
valueFormatted,
|
||||
data,
|
||||
}: {
|
||||
valueFormatted: string;
|
||||
data: StopOrder;
|
||||
}) => (
|
||||
<span data-testid={`order-status-${data?.id}`}>{valueFormatted}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'submission.price',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.price'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data?.market ||
|
||||
data.submission.type === Schema.OrderType.TYPE_MARKET ||
|
||||
!isNumeric(value)
|
||||
) {
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
cellRenderer: ({ data }: { data?: StopOrder }) => {
|
||||
if (!data) return null;
|
||||
},
|
||||
{
|
||||
field: 'submission.timeInForce',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTimeInForceMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.timeInForce'>) => {
|
||||
return value ? Schema.OrderTimeInForceCode[value] : '';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'updatedAt',
|
||||
filter: DateRangeFilter,
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) =>
|
||||
data?.updatedAt || data?.createdAt,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<StopOrder, 'createdAt'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
const value = data.updatedAt || data.createdAt;
|
||||
return (
|
||||
<span data-value={value}>
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
cellRenderer: ({ data }: { data?: StopOrder }) => {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
{data.status === Schema.StopOrderStatus.STATUS_PENDING &&
|
||||
!props.isReadOnly && (
|
||||
<ButtonLink
|
||||
data-testid="cancel"
|
||||
onClick={() => onCancel(data)}
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
{data.status === Schema.StopOrderStatus.STATUS_PENDING &&
|
||||
!props.isReadOnly && (
|
||||
<ButtonLink
|
||||
data-testid="cancel"
|
||||
onClick={() => onCancel(data)}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</ButtonLink>
|
||||
)}
|
||||
{data.status === Schema.StopOrderStatus.STATUS_TRIGGERED &&
|
||||
data.order && (
|
||||
<ActionsDropdown data-testid="stop-order-actions-content">
|
||||
<TradingDropdownCopyItem
|
||||
value={data.order.id}
|
||||
text={t('Copy order ID')}
|
||||
/>
|
||||
<DropdownMenuItem
|
||||
key={'view-order'}
|
||||
data-testid="view-order"
|
||||
onClick={() =>
|
||||
data.order &&
|
||||
onView({ ...data.order, market: data.market })
|
||||
}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</ButtonLink>
|
||||
)}
|
||||
{data.status === Schema.StopOrderStatus.STATUS_TRIGGERED &&
|
||||
data.order && (
|
||||
<ActionsDropdown data-testid="stop-order-actions-content">
|
||||
<TradingDropdownCopyItem
|
||||
value={data.order.id}
|
||||
text={t('Copy order ID')}
|
||||
/>
|
||||
<DropdownMenuItem
|
||||
key={'view-order'}
|
||||
data-testid="view-order"
|
||||
onClick={() =>
|
||||
data.order &&
|
||||
onView({ ...data.order, market: data.market })
|
||||
}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View order details')}
|
||||
</DropdownMenuItem>
|
||||
</ActionsDropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View order details')}
|
||||
</DropdownMenuItem>
|
||||
</ActionsDropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
],
|
||||
[onCancel, onMarketClick, onView, props.isReadOnly, showAllActions]
|
||||
);
|
||||
},
|
||||
],
|
||||
[onCancel, onMarketClick, onView, props.isReadOnly, showAllActions]
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -181,7 +181,7 @@ const getSubscriptionVariables = (
|
||||
): PositionsSubscriptionSubscriptionVariables[] =>
|
||||
([] as string[]).concat(variables.partyIds).map((partyId) => ({ partyId }));
|
||||
|
||||
export const positionsDataProvider = makeDataProvider<
|
||||
const positionsDataProvider = makeDataProvider<
|
||||
PositionsQuery,
|
||||
PositionFieldsFragment[],
|
||||
PositionsSubscriptionSubscription,
|
||||
|
||||
@@ -177,21 +177,7 @@ module.exports = {
|
||||
success: '#00F780',
|
||||
},
|
||||
fontFamily: {
|
||||
mono: [
|
||||
'ui-monospace',
|
||||
'Menlo',
|
||||
'Monaco',
|
||||
'Cascadia Mono',
|
||||
'Segoe UI Mono',
|
||||
'Roboto Mono',
|
||||
'Oxygen Mono',
|
||||
'Ubuntu Monospace',
|
||||
'Source Code Pro',
|
||||
'Fira Mono',
|
||||
'Droid Sans Mono',
|
||||
'Courier New',
|
||||
'monospace',
|
||||
],
|
||||
mono: ['Roboto Mono', 'monospace'],
|
||||
sans: [
|
||||
'"Helvetica Neue", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
|
||||
],
|
||||
|
||||
@@ -5,7 +5,7 @@ import { VegaIconNameMap } from './vega-icon-record';
|
||||
|
||||
export interface VegaIconProps {
|
||||
name: VegaIconNames;
|
||||
size?: 8 | 10 | 12 | 13 | 14 | 16 | 18 | 20 | 24 | 32;
|
||||
size?: 8 | 10 | 12 | 13 | 14 | 16 | 20 | 24 | 32;
|
||||
}
|
||||
|
||||
export const VegaIcon = ({ size = 16, name }: VegaIconProps) => {
|
||||
|
||||
@@ -34,7 +34,6 @@ export * from './progress-bar';
|
||||
export * from './radio-group';
|
||||
export * from './rounded-wrapper';
|
||||
export * from './select';
|
||||
export * from './show-more';
|
||||
export * from './simple-grid';
|
||||
export * from './slider';
|
||||
export * from './sparkline';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './show-more';
|
||||
@@ -1,9 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { ShowMore } from './show-more';
|
||||
|
||||
describe('Button', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<ShowMore>test</ShowMore>);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
import type { Story, Meta } from '@storybook/react';
|
||||
import { ShowMore } from './show-more';
|
||||
|
||||
export default {
|
||||
component: ShowMore,
|
||||
title: 'ShowMore',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story = (args) => (
|
||||
<ShowMore {...args}>
|
||||
<p>
|
||||
Spaceflight will never tolerate carelessness, incapacity, and neglect.
|
||||
Somewhere, somehow, we screwed up. It could have been in design, build, or
|
||||
test. Whatever it was, we should have caught it. We were too gung ho about
|
||||
the schedule and we locked out all of the problems we saw each day in our
|
||||
work. “Every element of the program was in trouble and so were we. The
|
||||
simulators were not working, Mission Control was behind in virtually every
|
||||
area, and the flight and test procedures changed daily. Nothing we did had
|
||||
any shelf life. Not one of us stood up and said, ‘Dammit, stop!’ I don’t
|
||||
know what Thompson’s committee will find as the cause, but I know what I
|
||||
find. We are the cause! We were not ready! We did not do our job. We were
|
||||
rolling the dice, hoping that things would come together by launch day,
|
||||
when in our hearts we knew it would take a miracle. We were pushing the
|
||||
schedule and betting that the Cape would slip before we did. “From this
|
||||
day forward, Flight Control will be known by two words: ‘Tough’ and
|
||||
‘Competent.’ Tough means we are forever accountable for what we do or what
|
||||
we fail to do. We will never again compromise our responsibilities. Every
|
||||
time we walk into Mission Control we will know what we stand for.
|
||||
Competent means we will never take anything for granted. We will never be
|
||||
found short in our knowledge and in our skills. Mission Control will be
|
||||
perfect. When you leave this meeting today you will go to your office and
|
||||
the first thing you will do there is to write ‘Tough and Competent’ on
|
||||
your blackboards. It will never be erased. Each day when you enter the
|
||||
room these words will remind you of the price paid by Grissom, White, and
|
||||
Chaffee. These words are the price of admission to the ranks of Mission
|
||||
Control.
|
||||
</p>
|
||||
</ShowMore>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
export const CustomMaxHeight = Template.bind({});
|
||||
CustomMaxHeight.args = {
|
||||
closedMaxHeightPx: 50,
|
||||
};
|
||||
|
||||
export const CustomOverlayColour = Template.bind({});
|
||||
CustomOverlayColour.args = {
|
||||
overlayColourOverrides: 'to-yellow-400',
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Button } from '../button';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
type ShowMoreProps = {
|
||||
children: ReactNode;
|
||||
closedMaxHeightPx?: number;
|
||||
overlayColourOverrides?: string;
|
||||
};
|
||||
|
||||
export const ShowMore = ({
|
||||
children,
|
||||
closedMaxHeightPx = 125,
|
||||
overlayColourOverrides,
|
||||
}: ShowMoreProps) => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkHeight = () => {
|
||||
const container = containerRef.current;
|
||||
if (container) {
|
||||
container.scrollHeight < closedMaxHeightPx
|
||||
? setExpanded(true)
|
||||
: setExpanded(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkHeight();
|
||||
|
||||
window.addEventListener('resize', checkHeight);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', checkHeight);
|
||||
};
|
||||
}, [closedMaxHeightPx]);
|
||||
|
||||
const containerClasses = classNames(
|
||||
'overflow-hidden transition-all ease-in-out duration-300',
|
||||
{
|
||||
'max-h-none': expanded,
|
||||
}
|
||||
);
|
||||
|
||||
const overlayClasses = classNames(
|
||||
`absolute w-full h-16 bottom-0 left-0 transition-opacity duration-300 bg-gradient-to-b from-transparent ${
|
||||
overlayColourOverrides ? overlayColourOverrides : 'to-white dark:to-black'
|
||||
}`,
|
||||
{
|
||||
hidden: expanded,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={containerClasses}
|
||||
style={{ maxHeight: expanded ? 'none' : `${closedMaxHeightPx}px` }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div className={overlayClasses}></div>
|
||||
</div>
|
||||
|
||||
{!expanded && (
|
||||
<div className="mt-1 text-center">
|
||||
<Button size={'sm'} onClick={() => setExpanded(true)}>
|
||||
{t('Show more')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -204,7 +204,6 @@ const ConnectorList = ({
|
||||
setWalletUrl: (value: string) => void;
|
||||
isDesktopWalletRunning: boolean | null;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const title = isBrowserWalletInstalled()
|
||||
? t('Connect Vega wallet')
|
||||
: t('Get a Vega wallet');
|
||||
@@ -235,7 +234,7 @@ const ConnectorList = ({
|
||||
onClick={() => onSelect('injected')}
|
||||
/>
|
||||
) : (
|
||||
<GetWalletButton />
|
||||
<GetWallet />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
@@ -243,7 +242,6 @@ const ConnectorList = ({
|
||||
type="view"
|
||||
text={t('View as party')}
|
||||
onClick={() => onSelect('view')}
|
||||
disabled={Boolean(pubKey)}
|
||||
/>
|
||||
</div>
|
||||
<div className="last:mb-0">
|
||||
@@ -320,7 +318,7 @@ const SelectedForm = ({
|
||||
throw new Error('No connector selected');
|
||||
};
|
||||
|
||||
export const GetWalletButton = ({ className }: { className?: string }) => {
|
||||
const GetWallet = () => {
|
||||
const { MOZILLA_EXTENSION_URL, CHROME_EXTENSION_URL } = useEnvironment();
|
||||
const isItChrome = window.navigator.userAgent.includes('Chrome');
|
||||
const isItMozilla =
|
||||
@@ -349,13 +347,10 @@ export const GetWalletButton = ({ className }: { className?: string }) => {
|
||||
|
||||
return !isItChrome && !isItMozilla ? (
|
||||
<div
|
||||
className={classNames(
|
||||
[
|
||||
'bg-vega-blue-350 hover:bg-vega-blue-400 dark:bg-vega-blue-650 dark:hover:bg-vega-blue-600',
|
||||
'flex gap-2 items-center justify-center rounded h-8 px-3 relative',
|
||||
],
|
||||
className
|
||||
)}
|
||||
className={classNames([
|
||||
'bg-vega-blue-350 hover:bg-vega-blue-400 dark:bg-vega-blue-650 dark:hover:bg-vega-blue-600',
|
||||
'flex gap-2 items-center justify-center rounded h-8 px-3 relative',
|
||||
])}
|
||||
data-testid="get-wallet-button"
|
||||
>
|
||||
{buttonContent}
|
||||
@@ -365,7 +360,7 @@ export const GetWalletButton = ({ className }: { className?: string }) => {
|
||||
onClick={onClick}
|
||||
intent={Intent.Info}
|
||||
data-testid="get-wallet-button"
|
||||
className={classNames('relative', className)}
|
||||
className="relative"
|
||||
size="small"
|
||||
fill
|
||||
>
|
||||
@@ -414,7 +409,6 @@ const CustomUrlInput = ({
|
||||
isDesktopWalletRunning: boolean | null;
|
||||
onSelect: (type: WalletType) => void;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const [urlInputExpanded, setUrlInputExpanded] = useState(false);
|
||||
return urlInputExpanded ? (
|
||||
<>
|
||||
@@ -439,7 +433,7 @@ const CustomUrlInput = ({
|
||||
/>
|
||||
</TradingFormGroup>
|
||||
<ConnectionOption
|
||||
disabled={!isDesktopWalletRunning || Boolean(pubKey)}
|
||||
disabled={!isDesktopWalletRunning}
|
||||
type="jsonRpc"
|
||||
text={t('Connect the App/CLI')}
|
||||
onClick={() => onSelect('jsonRpc')}
|
||||
@@ -448,7 +442,7 @@ const CustomUrlInput = ({
|
||||
) : (
|
||||
<>
|
||||
<ConnectionOption
|
||||
disabled={!isDesktopWalletRunning || Boolean(pubKey)}
|
||||
disabled={!isDesktopWalletRunning}
|
||||
type="jsonRpc"
|
||||
text={t('Use the Desktop App/CLI')}
|
||||
onClick={() => onSelect('jsonRpc')}
|
||||
@@ -459,7 +453,6 @@ const CustomUrlInput = ({
|
||||
<button
|
||||
className="underline text-default"
|
||||
onClick={() => setUrlInputExpanded(true)}
|
||||
disabled={Boolean(pubKey)}
|
||||
>
|
||||
{t('Enter a custom wallet location')}{' '}
|
||||
<VegaIcon name={VegaIconNames.ARROW_RIGHT} />
|
||||
@@ -474,7 +467,6 @@ const CustomUrlInput = ({
|
||||
<button
|
||||
className="underline"
|
||||
onClick={() => setUrlInputExpanded(true)}
|
||||
disabled={Boolean(pubKey)}
|
||||
>
|
||||
{t('custom wallet location')}
|
||||
</button>
|
||||
|
||||
@@ -12,9 +12,7 @@ export const useIsWalletServiceRunning = (
|
||||
|
||||
const checkState = useCallback(async () => {
|
||||
const connector = connectors['jsonRpc'] as JsonRpcConnector;
|
||||
if (url && url !== connector.url) {
|
||||
connector.url = url;
|
||||
}
|
||||
connector.url = url;
|
||||
try {
|
||||
await connector.checkCompat();
|
||||
const chainIdResult = await connector.getChainId();
|
||||
|
||||
@@ -9,7 +9,6 @@ import type {
|
||||
VegaStoredTxState,
|
||||
WithdrawalBusEventFieldsFragment,
|
||||
StopOrdersSubmission,
|
||||
StopOrderSetup,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import {
|
||||
isTransferTransaction,
|
||||
@@ -50,7 +49,6 @@ import {
|
||||
useOrderByIdQuery,
|
||||
useStopOrderByIdQuery,
|
||||
} from '@vegaprotocol/orders';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { useMarketsMapProvider } from '@vegaprotocol/markets';
|
||||
import type { Side } from '@vegaprotocol/types';
|
||||
import { OrderStatusMapping } from '@vegaprotocol/types';
|
||||
@@ -176,15 +174,11 @@ const SubmitOrderDetails = ({
|
||||
);
|
||||
};
|
||||
|
||||
const SubmitStopOrderSetup = ({
|
||||
stopOrderSetup,
|
||||
triggerDirection,
|
||||
market,
|
||||
}: {
|
||||
stopOrderSetup: StopOrderSetup;
|
||||
triggerDirection: Schema.StopOrderTriggerDirection;
|
||||
market: Market;
|
||||
}) => {
|
||||
const SubmitStopOrderDetails = ({ data }: { data: StopOrdersSubmission }) => {
|
||||
const { data: markets } = useMarketsMapProvider();
|
||||
const stopOrderSetup = data.risesAbove || data.fallsBelow;
|
||||
if (!stopOrderSetup) return null;
|
||||
const market = markets?.[stopOrderSetup?.orderSubmission.marketId];
|
||||
if (!market || !stopOrderSetup) return null;
|
||||
|
||||
const { price, size, side } = stopOrderSetup.orderSubmission;
|
||||
@@ -197,64 +191,37 @@ const SubmitStopOrderSetup = ({
|
||||
__typename: 'StopOrderTrailingPercentOffset',
|
||||
};
|
||||
}
|
||||
return (
|
||||
<p>
|
||||
<SizeAtPrice
|
||||
meta={{
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
asset:
|
||||
market.tradableInstrument.instrument.product.settlementAsset.symbol,
|
||||
}}
|
||||
side={side}
|
||||
size={size}
|
||||
price={price}
|
||||
/>
|
||||
<br />
|
||||
{trigger &&
|
||||
formatTrigger(
|
||||
{
|
||||
triggerDirection,
|
||||
trigger,
|
||||
},
|
||||
market.decimalPlaces,
|
||||
''
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
const SubmitStopOrderDetails = ({ data }: { data: StopOrdersSubmission }) => {
|
||||
const { data: markets } = useMarketsMapProvider();
|
||||
const marketId =
|
||||
data.fallsBelow?.orderSubmission.marketId ||
|
||||
data.risesAbove?.orderSubmission.marketId;
|
||||
const market = marketId && markets?.[marketId];
|
||||
if (!market) {
|
||||
return null;
|
||||
}
|
||||
const triggerDirection = data.risesAbove
|
||||
? Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
: Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW;
|
||||
return (
|
||||
<Panel>
|
||||
<h4>{t('Submit stop order')}</h4>
|
||||
<p>{market?.tradableInstrument.instrument.code}</p>
|
||||
{data.fallsBelow && (
|
||||
<SubmitStopOrderSetup
|
||||
stopOrderSetup={data.fallsBelow}
|
||||
triggerDirection={
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
}
|
||||
market={market}
|
||||
<p>
|
||||
<SizeAtPrice
|
||||
meta={{
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
asset:
|
||||
market.tradableInstrument.instrument.product.settlementAsset
|
||||
.symbol,
|
||||
}}
|
||||
side={side}
|
||||
size={size}
|
||||
price={price}
|
||||
/>
|
||||
)}
|
||||
{data.risesAbove && (
|
||||
<SubmitStopOrderSetup
|
||||
stopOrderSetup={data.risesAbove}
|
||||
triggerDirection={
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
}
|
||||
market={market}
|
||||
/>
|
||||
)}
|
||||
<br />
|
||||
{trigger &&
|
||||
formatTrigger(
|
||||
{
|
||||
triggerDirection,
|
||||
trigger,
|
||||
},
|
||||
market.decimalPlaces,
|
||||
''
|
||||
)}
|
||||
</p>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
"echo $NX_TENDERMINT_URL",
|
||||
"echo $NX_TENDERMINT_WEBSOCKET_URL",
|
||||
"echo $NX_ETHEREUM_PROVIDER_URL"
|
||||
]
|
||||
],
|
||||
"url": "https://cloud.nx.app"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
# First use & get started steps
|
||||
|
||||
## "Onboarding" state is gradable and has following steps: (<a name="0007-FUGS-002" href="#0007-FUGS-002">0007-FUGS-003</a>)
|
||||
|
||||
- New visitor - has no wallet nor any dapps running.
|
||||
- I **must** see CTA button "Get started", which clicking launches the get wallet flow (wallet connection window with links to the Chrome and FF stores)
|
||||
- Has wallet - Once wallet detected set to this.
|
||||
- I **must** see CTA button "Connect", which clicking launches the wallet connection
|
||||
- Has connected - Once user has connected set to this.
|
||||
- I **must** see CTA button "Deposit", which clicking launches the deposit ticket.
|
||||
- Has deposited - Once user has made AT LEAST one deposit of ANY settlement asset set to this.
|
||||
- I **must** see CTA button "Dismiss", which clicking updates the state to Ready to trade - Get started box should now disappear forever.
|
||||
- Ready to trade - Once user has made at least one deposit AND has dismissed the "Get Started" box in the ticket.
|
||||
- Onboarding window nor contextual "Get started" banner should be not displayed anymore.
|
||||
|
||||
## When first enter the app or next times, but I didn't accomplish all onboarding steps.
|
||||
## When first enter the app
|
||||
|
||||
- **Must** When I open Console for the first time I can see what it is i.e. a short description and key features in auto opened dialog window (first use popup) (<a name="0007-FUGS-001" href="#0007-FUGS-001">0007-FUGS-001</a>)
|
||||
- If full "onboarding" hasn't been accomplished yet, I **must** see the popup with my progress marked. (<a name="0007-FUGS-002" href="#0007-FUGS-002">0007-FUGS-002</a>)
|
||||
- **Must** If my wallet is already connected I don't see the first use popup (<a name="0007-FUGS-002" href="#0007-FUGS-002">0007-FUGS-002</a>)
|
||||
- - **Must** If window.vega is detected (browser wallet is installed), don't open first use popup (<a name="0007-FUGS-003" href="#0007-FUGS-003">0007-FUGS-003</a>)
|
||||
- - **Must** If we detect previous connection using localStorage for desktop/cli wallet, don't open first use popup (<a name="0007-FUGS-004" href="#0007-FUGS-004">0007-FUGS-004</a>)
|
||||
- **Must** There is a call to action to browse markets, linking to the market view market/all (<a name="0007-FUGS-005" href="#0007-FUGS-005">0007-FUGS-005</a>)
|
||||
- **Must** I can see the steps I need to take to get started trading (<a name="0007-FUGS-007" href="#0007-FUGS-006">0007-FUGS-006</a>)
|
||||
- **Must** There is a call to action to get started, triggering the connect modal (<a name="0007-FUGS-007" href="#0007-FUGS-007">0007-FUGS-007</a>)
|
||||
- **Must** There is a link to try out trading on Fairground when I'm on Mainnet (<a name="0007-FUGS-008" href="#0007-FUGS-008">0007-FUGS-008</a>)
|
||||
- **Must** There is a link to trade with real funds on Mainnet when I am on Fairground (<a name="0007-FUGS-010" href="#0007-FUGS-010">0007-FUGS-010</a>)
|
||||
- **Must** When I am on the Fairground version, I can see a warning / call out that this is Fairground meaning I can try out with virtual assets at no risk (<a name="0007-FUGS-011" href="#0007-FUGS-011">0007-FUGS-011</a>)
|
||||
- If I dismiss the popup, I **must** not see it unless I NOT accomplish full "onboarding"
|
||||
- If I dismiss the popup, I land on the default market (<a name="0007-FUGS-012" href="#0007-FUGS-012">0007-FUGS-012</a>)
|
||||
|
||||
## When the popup has been dismissed:
|
||||
## When first use popup has been seen, but no browser wallet is installed
|
||||
|
||||
- **Must** I can see the steps to get started with a visible call to action (according to my progress) in the context of the deal ticket, deposit, withdraw, transfer components in the sidebar (<a name="0007-FUGS-013" href="#0007-FUGS-013">0007-FUGS-013</a>)
|
||||
- **Must** I can see the steps to get started with a visible call to action to "get started" in the context of the deal ticket, deposit, withdraw, transfer components in the sidebar (<a name="0007-FUGS-013" href="#0007-FUGS-013">0007-FUGS-013</a>)
|
||||
- **Must** Remove buttons from pane containers that prompt to connect wallet (<a name="0007-FUGS-014" href="#0007-FUGS-014">0007-FUGS-014</a>)
|
||||
- **Must** We've replaced "connect wallet" in the top right with "get started" (<a name="0007-FUGS-015" href="#0007-FUGS-015">0007-FUGS-015</a>)
|
||||
- **Must** When I press the get started CTA, I see the wallet connect popup (<a name="0007-FUGS-016" href="#0007-FUGS-016">0007-FUGS-016</a>)
|
||||
|
||||
Reference in New Issue
Block a user