Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c5eb0f3eb | ||
|
|
d6a8134f97 | ||
|
|
ffdf4f85ae | ||
|
|
0d1a8a5d3f | ||
|
|
49d4046cc1 |
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Feature Epic
|
||||
about: A template to capture and scope user requirements, high level process, and basic mockups for an upcoming feature as part of the initial core spec review process.
|
||||
title: 'FEATURE EPIC: '
|
||||
title: 'Epic: '
|
||||
labels: feature-epic
|
||||
---
|
||||
|
||||
|
||||
@@ -31,15 +31,17 @@ context('Asset page', { tags: '@regression' }, () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should open details page when clicked on "View details"', () => {
|
||||
it.skip('should open details page when clicked on "View details"', () => {
|
||||
cy.getAssets().then((assets) => {
|
||||
// Set to only click through one asset as the navigation could be
|
||||
// causing a 'ResizeLoopObserver' error to be thrown inappropriately
|
||||
assets.slice(0, 1).forEach((asset) => {
|
||||
assets.forEach((asset) => {
|
||||
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
|
||||
.eq(0)
|
||||
.should('contain.text', 'View details');
|
||||
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
|
||||
.eq(0)
|
||||
.click();
|
||||
cy.getByTestId('asset-header').should('have.text', asset.name);
|
||||
cy.go('back');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
|
||||
@@ -8,3 +6,6 @@ NX_VEGA_EXPLORER_URL=/
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_TXS_LIST=0
|
||||
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
|
||||
NX_VEGA_ENV=DEVNET
|
||||
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
|
||||
@@ -10,3 +8,6 @@ NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_EXPLORER_URL=/
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
NX_SENTRY_DSN=https://b3a56b03eda842faad731f3ea9dfd1bc@o286262.ingest.sentry.io/6242427
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
|
||||
NX_VEGA_URL=https://api.vega.community/graphql
|
||||
@@ -11,3 +9,6 @@ NX_VEGA_GOVERNANCE_URL=https://governance.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz/
|
||||
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
|
||||
NX_VEGA_URL=https://api.mainnet-mirror.vega.rocks/graphql
|
||||
@@ -10,4 +8,7 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://governance.mainnet-mirror.vega.rocks
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks/
|
||||
NX_VEGA_CONSOLE_URL=https://console.mainnet-mirror.vega.rocks
|
||||
NX_VEGA_CONSOLE_URL=https://console.mainnet-mirror.vega.rocks
|
||||
|
||||
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
|
||||
@@ -11,3 +9,6 @@ NX_VEGA_GOVERNANCE_URL=https://governance.fairground.wtf
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
@@ -7,9 +7,10 @@ NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
||||
NX_BLOCK_EXPLORER=https://be.validators-testnet.vega.rocks/rest
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
|
||||
NX_VEGA_GOVERNANCE_URL=https://governance.validators-testnet.vega.rocks
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks/
|
||||
NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks/
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
|
||||
|
||||
@@ -22,6 +22,11 @@ import { Footer } from '../components/footer/footer';
|
||||
import { Header } from '../components/header';
|
||||
import { Routes } from './route-names';
|
||||
import { useExplorerNodeNamesLazyQuery } from './validators/__generated__/NodeNames';
|
||||
import {
|
||||
ProtocolUpgradeCountdownMode,
|
||||
ProtocolUpgradeInProgressNotification,
|
||||
ProtocolUpgradeProposalNotification,
|
||||
} from '@vegaprotocol/proposals';
|
||||
|
||||
const DialogsContainer = () => {
|
||||
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
|
||||
@@ -62,6 +67,10 @@ export const Layout = () => {
|
||||
/>
|
||||
)}
|
||||
<Header />
|
||||
<ProtocolUpgradeProposalNotification
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
<ProtocolUpgradeInProgressNotification />
|
||||
</div>
|
||||
<div className={fixedWidthClasses}>
|
||||
<main className="p-4">
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 547 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"short_name": "Mainnet Stats",
|
||||
"name": "Vega Mainnet statistics",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -1,39 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta charset="utf-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Vega Protocol - VEGA Explorer" />
|
||||
<meta name="og:type" content="website" />
|
||||
<meta name="og:url" content="https://explorer.vega.xyz/" />
|
||||
<meta name="og:title" content="Vega Protocol - Explorer" />
|
||||
<meta name="og:site_name" content="Vega Protocol - Explorer" />
|
||||
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Vega Protocol - Explorer" />
|
||||
<meta name="twitter:description" content="Vega Protocol - Explorer" />
|
||||
<meta name="twitter:image" content="https://static.vega.xyz/favicon.ico" />
|
||||
<meta name="twitter:image:alt" content="VEGA logo" />
|
||||
<meta name="twitter:site" content="@vegaprotocol" />
|
||||
|
||||
<meta name="description" content="Vega Protocol - Explorer" />
|
||||
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="assets/manifest.json" />
|
||||
<title>Explorer</title>
|
||||
<base href="/" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<title>VEGA Explorer</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||
<script src="./assets/env-config.js"></script>
|
||||
</head>
|
||||
<body class="dark:bg-black h-full w-full">
|
||||
|
||||
@@ -18,7 +18,6 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
|
||||
@@ -11,9 +11,7 @@ import {
|
||||
getDateFormatForSpecifiedDays,
|
||||
getProposalFromTitle,
|
||||
getProposalInformationFromTable,
|
||||
proposalChangeType,
|
||||
submitUniqueRawProposal,
|
||||
validateProposalDetailsDiff,
|
||||
voteForProposal,
|
||||
} from '../../../../governance-e2e/src/support/governance.functions';
|
||||
import {
|
||||
@@ -28,9 +26,7 @@ import {
|
||||
} from '../../support/wallet-functions';
|
||||
import type { testFreeformProposal } from '../../support/common-interfaces';
|
||||
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
|
||||
import { createSuccessorMarketProposalTxBody } from '../../support/proposal.functions';
|
||||
|
||||
const proposalListItem = '[data-testid="proposals-list-item"]';
|
||||
const proposalVoteProgressForPercentage =
|
||||
'vote-progress-indicator-percentage-for';
|
||||
const proposalVoteProgressAgainstPercentage =
|
||||
@@ -46,7 +42,6 @@ const viewProposalButton = 'view-proposal-btn';
|
||||
const proposalDescriptionToggle = 'proposal-description-toggle';
|
||||
const voteBreakdownToggle = 'vote-breakdown-toggle';
|
||||
const proposalTermsToggle = 'proposal-json-toggle';
|
||||
const marketDataToggle = 'proposal-market-data-toggle';
|
||||
|
||||
describe(
|
||||
'Governance flow for proposal details',
|
||||
@@ -359,98 +354,5 @@ describe(
|
||||
switchVegaWalletPubKey();
|
||||
stakingPageDisassociateAllTokens();
|
||||
});
|
||||
|
||||
it('Able to see successor market details with new and updated values', function () {
|
||||
cy.createMarket();
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
cy.getByTestId('closed-proposals').within(() => {
|
||||
cy.contains('Add Lorem Ipsum market')
|
||||
.parentsUntil(proposalListItem)
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.getByTestId(viewProposalButton).click();
|
||||
});
|
||||
});
|
||||
getProposalInformationFromTable('ID')
|
||||
.invoke('text')
|
||||
.as('parentMarketId')
|
||||
.then(() => {
|
||||
cy.VegaWalletSubmitProposal(
|
||||
createSuccessorMarketProposalTxBody(this.parentMarketId)
|
||||
);
|
||||
});
|
||||
navigateTo(navigation.proposals);
|
||||
cy.reload();
|
||||
getProposalFromTitle('Test successor market proposal details').within(
|
||||
() => cy.getByTestId(viewProposalButton).click()
|
||||
);
|
||||
// #3003-PMAN-010
|
||||
cy.getByTestId(proposalTermsToggle).click();
|
||||
cy.get('.language-json').within(() => {
|
||||
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
|
||||
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
|
||||
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
|
||||
cy.get('.hljs-string').should('contain.text', '0.75');
|
||||
});
|
||||
// 3003-PMAN-011 3003-PMAN-012
|
||||
cy.getByTestId(marketDataToggle).click();
|
||||
cy.getByTestId('proposal-market-data').within(() => {
|
||||
cy.contains('Key details').click();
|
||||
validateProposalDetailsDiff(
|
||||
'Name',
|
||||
proposalChangeType.UPDATED,
|
||||
'Token test market',
|
||||
'Test market 1'
|
||||
);
|
||||
validateProposalDetailsDiff(
|
||||
'Parent Market ID',
|
||||
proposalChangeType.ADDED,
|
||||
this.parentMarketId
|
||||
);
|
||||
validateProposalDetailsDiff(
|
||||
'Insurance Pool Fraction',
|
||||
proposalChangeType.ADDED,
|
||||
'0.75'
|
||||
);
|
||||
validateProposalDetailsDiff(
|
||||
'Trading Mode',
|
||||
proposalChangeType.UPDATED,
|
||||
'No trading',
|
||||
'Opening auction'
|
||||
);
|
||||
|
||||
cy.contains('Instrument').click();
|
||||
validateProposalDetailsDiff(
|
||||
'Market Name',
|
||||
proposalChangeType.UPDATED,
|
||||
'Token test market',
|
||||
'Test market 1'
|
||||
);
|
||||
|
||||
cy.contains('Metadata').click();
|
||||
validateProposalDetailsDiff(
|
||||
'Sector',
|
||||
proposalChangeType.UPDATED,
|
||||
'materials',
|
||||
'tech'
|
||||
);
|
||||
});
|
||||
|
||||
// 3003-PMAN-011
|
||||
cy.get('.underline').contains('Parent Market ID').realHover();
|
||||
cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
|
||||
'contain.text',
|
||||
'The ID of the market this market succeeds.'
|
||||
);
|
||||
cy.get('.underline')
|
||||
.contains('Insurance Pool Fraction')
|
||||
.realMouseUp()
|
||||
.realHover();
|
||||
cy.getByTestId('tooltip-content', { timeout: 8000 }).should(
|
||||
'contain.text',
|
||||
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -654,7 +654,44 @@ context(
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', this.parentMarketId);
|
||||
cy.getByTestId('view-proposal-btn').click();
|
||||
});
|
||||
// #3003-PMAN-010
|
||||
cy.getByTestId(proposalJsonToggle).click();
|
||||
cy.get('.language-json').within(() => {
|
||||
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
|
||||
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
|
||||
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
|
||||
cy.get('.hljs-string').should('contain.text', '0.75');
|
||||
});
|
||||
cy.getByTestId('proposal-market-data').within(() => {
|
||||
cy.getByTestId('proposal-market-data-toggle').click();
|
||||
cy.contains('Key details').click();
|
||||
// 3003-PMAN-009
|
||||
getMarketProposalDetailsFromTable('Parent Market ID').should(
|
||||
'have.text',
|
||||
this.parentMarketId
|
||||
);
|
||||
getMarketProposalDetailsFromTable('Insurance Pool Fraction').should(
|
||||
'have.text',
|
||||
'0.75'
|
||||
);
|
||||
getMarketProposalDetailsFromTable('Trading Mode').should(
|
||||
'have.text',
|
||||
'No trading'
|
||||
);
|
||||
});
|
||||
// 3003-PMAN-011
|
||||
cy.contains('Parent Market ID').realHover();
|
||||
cy.getByTestId('tooltip-content').should(
|
||||
'contain.text',
|
||||
'The ID of the market this market succeeds.'
|
||||
);
|
||||
cy.contains('Insurance Pool Fraction').realHover();
|
||||
cy.getByTestId('tooltip-content').should(
|
||||
'contain.text',
|
||||
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
|
||||
);
|
||||
});
|
||||
|
||||
after('Disassociate from second wallet key if present', function () {
|
||||
|
||||
@@ -232,23 +232,6 @@ export function getDownloadedProposalJsonPath(proposalType: string) {
|
||||
return filepath;
|
||||
}
|
||||
|
||||
export function validateProposalDetailsDiff(
|
||||
RowName: string,
|
||||
changeType: proposalChangeType,
|
||||
newValue: string,
|
||||
oldValue?: string
|
||||
) {
|
||||
cy.contains(RowName)
|
||||
.parentsUntil(proposalInformationTableRows)
|
||||
.parent()
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.contains(changeType).should('be.visible');
|
||||
cy.contains(newValue).should('be.visible');
|
||||
if (oldValue) cy.contains(oldValue).should('have.class', 'line-through');
|
||||
});
|
||||
}
|
||||
|
||||
function getFormattedTime() {
|
||||
const now = new Date();
|
||||
const day = now.getDate().toString().padStart(2, '0');
|
||||
@@ -269,8 +252,3 @@ export enum governanceProposalType {
|
||||
FREEFORM = 'Freeform',
|
||||
RAW = 'raw proposal',
|
||||
}
|
||||
|
||||
export enum proposalChangeType {
|
||||
UPDATED = 'Updated',
|
||||
ADDED = 'Added',
|
||||
}
|
||||
|
||||
@@ -211,143 +211,6 @@ export function createNewMarketProposalTxBody(): ProposalSubmissionBody {
|
||||
};
|
||||
}
|
||||
|
||||
// Requires cy.createMarket() to be run to set up parent market
|
||||
export function createSuccessorMarketProposalTxBody(
|
||||
parentMarketId: string
|
||||
): ProposalSubmissionBody {
|
||||
const MIN_CLOSE_SEC = 10000;
|
||||
const MIN_ENACT_SEC = 10000;
|
||||
|
||||
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: {
|
||||
title: 'Test successor market proposal details',
|
||||
description: 'E2E test for successor market',
|
||||
},
|
||||
terms: {
|
||||
newMarket: {
|
||||
changes: {
|
||||
decimalPlaces: '5',
|
||||
positionDecimalPlaces: '5',
|
||||
linearSlippageFactor: '0.001',
|
||||
quadraticSlippageFactor: '0',
|
||||
lpPriceRange: '10',
|
||||
instrument: {
|
||||
name: 'Token test market',
|
||||
code: 'TEST.24h',
|
||||
future: {
|
||||
settlementAsset:
|
||||
'816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
|
||||
quoteName: 'fUSDC',
|
||||
dataSourceSpecForSettlementData: {
|
||||
external: {
|
||||
oracle: {
|
||||
signers: [
|
||||
{
|
||||
pubKey: {
|
||||
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
|
||||
},
|
||||
},
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
key: {
|
||||
name: 'prices.BTC.value',
|
||||
type: 'TYPE_INTEGER' as const,
|
||||
numberDecimalPlaces: '0',
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
operator: 'OPERATOR_GREATER_THAN' as const,
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
external: {
|
||||
oracle: {
|
||||
signers: [
|
||||
{
|
||||
pubKey: {
|
||||
key: '70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680',
|
||||
},
|
||||
},
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
key: {
|
||||
name: 'trading.terminated.ETH5',
|
||||
type: 'TYPE_BOOLEAN' as const,
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
operator: 'OPERATOR_EQUALS' as const,
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
dataSourceSpecBinding: {
|
||||
settlementDataProperty: 'prices.BTC.value',
|
||||
tradingTerminationProperty: 'trading.terminated.ETH5',
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: [
|
||||
'sector:food',
|
||||
'sector:materials',
|
||||
'source:docs.vega.xyz',
|
||||
],
|
||||
priceMonitoringParameters: {
|
||||
triggers: [
|
||||
{
|
||||
horizon: '43200',
|
||||
probability: '0.9999999',
|
||||
auctionExtension: '600',
|
||||
},
|
||||
],
|
||||
},
|
||||
liquidityMonitoringParameters: {
|
||||
targetStakeParameters: {
|
||||
timeWindow: '3600',
|
||||
scalingFactor: 10,
|
||||
},
|
||||
triggeringRatio: '0.7',
|
||||
auctionExtension: '1',
|
||||
},
|
||||
logNormal: {
|
||||
tau: 0.0001140771161,
|
||||
riskAversionParameter: 0.01,
|
||||
params: {
|
||||
mu: 0,
|
||||
r: 0.016,
|
||||
sigma: 0.5,
|
||||
},
|
||||
},
|
||||
successor: {
|
||||
parentMarketId: parentMarketId,
|
||||
insurancePoolFraction: '0.75',
|
||||
},
|
||||
},
|
||||
},
|
||||
closingTimestamp,
|
||||
enactmentTimestamp,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function mockNetworkUpgradeProposal() {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Nodes', nodeData);
|
||||
|
||||
@@ -9,7 +9,6 @@ NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","STAGNET1":"https:
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
|
||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
||||
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Nav } from '../nav';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
ProtocolUpgradeCountdownMode,
|
||||
ProtocolUpgradeInProgressNotification,
|
||||
ProtocolUpgradeProposalNotification,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
|
||||
@@ -49,6 +50,7 @@ const NotificationsContainer = () => {
|
||||
<ProtocolUpgradeProposalNotification
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
<ProtocolUpgradeInProgressNotification />
|
||||
{isReadOnly ? (
|
||||
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
|
||||
) : null}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark bg-black w-full h-full">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="//static.vega.xyz/fonts.css" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
@@ -10,42 +10,35 @@
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Vega Protocol - VEGA Governance" />
|
||||
<meta name="og:type" content="website" />
|
||||
<meta name="og:url" content="https://governance.vega.xyz/" />
|
||||
<meta name="og:title" content="Vega Protocol - Governance" />
|
||||
<meta name="og:site_name" content="Vega Protocol - Governance" />
|
||||
<meta name="og:image" content="https://static.vega.xyz/favicon.ico" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Vega Protocol - Governance" />
|
||||
<meta name="twitter:description" content="Vega Protocol - Governance" />
|
||||
<meta name="twitter:image" content="https://static.vega.xyz/favicon.ico" />
|
||||
<meta name="twitter:image:alt" content="VEGA logo" />
|
||||
<meta name="twitter:site" content="@vegaprotocol" />
|
||||
|
||||
<meta name="description" content="Vega Protocol - Governance" />
|
||||
<meta name="description" content="Vega Protocol - VEGA Token Vesting" />
|
||||
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="assets/manifest.json" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<title>VEGA Governance</title>
|
||||
<title>Vega Governance dApp</title>
|
||||
<script src="./assets/env-config.js"></script>
|
||||
</head>
|
||||
<body class="h-full">
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root" class="h-full"></div>
|
||||
<!-- This HTML file is a template.
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`. -->
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
|
||||
import type { MarketInfo } from '@vegaprotocol/markets';
|
||||
import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
||||
import type { DataSourceDefinition } from '@vegaprotocol/types';
|
||||
import { create } from 'zustand';
|
||||
|
||||
@@ -47,8 +47,8 @@ export const ProposalMarketData = ({
|
||||
marketData,
|
||||
parentMarketData,
|
||||
}: {
|
||||
marketData: MarketInfo;
|
||||
parentMarketData?: MarketInfo;
|
||||
marketData: MarketInfoWithData;
|
||||
parentMarketData?: MarketInfoWithData;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, open, close } = useMarketDataDialogStore();
|
||||
|
||||
@@ -13,7 +13,7 @@ import Routes from '../../../routes';
|
||||
import { ProposalMarketData } from '../proposal-market-data';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { MarketInfo } from '@vegaprotocol/markets';
|
||||
import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
||||
import type { AssetQuery } from '@vegaprotocol/assets';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
@@ -31,8 +31,8 @@ export enum ProposalType {
|
||||
export interface ProposalProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
networkParams: Partial<NetworkParamsResult>;
|
||||
newMarketData?: MarketInfo | null;
|
||||
parentMarketData?: MarketInfo | null;
|
||||
newMarketData?: MarketInfoWithData | null;
|
||||
parentMarketData?: MarketInfoWithData | null;
|
||||
assetData?: AssetQuery | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
restData: any;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useProposalQuery } from './__generated__/Proposal';
|
||||
import { useFetch } from '@vegaprotocol/react-helpers';
|
||||
import { ENV } from '../../../config';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { marketInfoProvider } from '@vegaprotocol/markets';
|
||||
import { marketInfoWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { useAssetQuery } from '@vegaprotocol/assets';
|
||||
import {
|
||||
NetworkParams,
|
||||
@@ -95,7 +95,7 @@ export const ProposalContainer = () => {
|
||||
loading: newMarketLoading,
|
||||
error: newMarketError,
|
||||
} = useDataProvider({
|
||||
dataProvider: marketInfoProvider,
|
||||
dataProvider: marketInfoWithDataProvider,
|
||||
skipUpdates: true,
|
||||
variables: {
|
||||
marketId: data?.proposal?.id || '',
|
||||
@@ -109,9 +109,12 @@ export const ProposalContainer = () => {
|
||||
error: parentMarketIdError,
|
||||
} = useParentMarketIdQuery({
|
||||
variables: {
|
||||
marketId: newMarketData?.id || '',
|
||||
marketId: newMarketData?.data?.market?.id || '',
|
||||
},
|
||||
skip: !FLAGS.SUCCESSOR_MARKETS || !isSuccessor || !newMarketData?.id,
|
||||
skip:
|
||||
!FLAGS.SUCCESSOR_MARKETS ||
|
||||
!isSuccessor ||
|
||||
!newMarketData?.data?.market?.id,
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -119,7 +122,7 @@ export const ProposalContainer = () => {
|
||||
loading: parentMarketLoading,
|
||||
error: parentMarketError,
|
||||
} = useDataProvider({
|
||||
dataProvider: marketInfoProvider,
|
||||
dataProvider: marketInfoWithDataProvider,
|
||||
skipUpdates: true,
|
||||
variables: {
|
||||
marketId: parentMarketId?.market?.parentMarketID || '',
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { marketsQuery } from '@vegaprotocol/mock';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
|
||||
describe('markets table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.clearLocalStorage().then(() => {
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
});
|
||||
});
|
||||
|
||||
it('opening auction subsets should be properly displayed', () => {
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION
|
||||
);
|
||||
cy.mockGQL((req) => {
|
||||
const override = {
|
||||
marketsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: `opening auction MARKET`,
|
||||
},
|
||||
},
|
||||
state: Schema.MarketState.STATE_ACTIVE,
|
||||
tradingMode:
|
||||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
// @ts-ignore partial deep check failing
|
||||
const market = marketsQuery(override);
|
||||
aliasGQLQuery(req, 'Market', market);
|
||||
aliasGQLQuery(req, 'ProposalOfMarket', {
|
||||
proposal: { terms: { enactmentDatetime: '2023-01-31 12:00:01' } },
|
||||
});
|
||||
});
|
||||
cy.visit('#/markets/market-0');
|
||||
cy.url().should('contain', 'market-0');
|
||||
cy.getByTestId('item-value').contains('Opening auction').realHover();
|
||||
cy.getByTestId('opening-auction-sub-status').should(
|
||||
'contain.text',
|
||||
'Opening auction: Not enough liquidity to open'
|
||||
);
|
||||
|
||||
const now = new Date(Date.parse('2023-01-30 12:00:01')).getTime();
|
||||
cy.clock(now, ['Date']); // Set "now" to BEFORE reservation
|
||||
cy.reload();
|
||||
cy.getByTestId('item-value').contains('Opening auction').realHover();
|
||||
cy.getByTestId('opening-auction-sub-status').should(
|
||||
'contain.text',
|
||||
`Opening auction: Closing on ${getDateTimeFormat().format(
|
||||
new Date('2023-01-31 12:00:01')
|
||||
)}`
|
||||
);
|
||||
cy.clock().then((clock) => {
|
||||
clock.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
@@ -20,3 +21,6 @@ NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
@@ -20,4 +20,7 @@ NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supp
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
|
||||
@@ -18,4 +18,7 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
|
||||
@@ -21,3 +21,6 @@ NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
@@ -20,4 +20,7 @@ NX_APP_VERSION=v0.20.19-core-0.71.6
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
|
||||
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
@@ -19,4 +19,7 @@ NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
@@ -19,4 +19,7 @@ NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.xyz/websocket
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1 +0,0 @@
|
||||
window._env_ = {};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 547 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"short_name": "Mainnet Stats",
|
||||
"name": "Vega Mainnet statistics",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -10,6 +10,7 @@ import { TradeGrid } from './trade-grid';
|
||||
import { TradePanels } from './trade-panels';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
|
||||
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
|
||||
@@ -64,6 +65,8 @@ export const MarketPage = () => {
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const lastMarketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const onSelect = useMarketClickHandler();
|
||||
|
||||
const { data, error, loading } = useMarket(marketId);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -84,6 +87,7 @@ export const MarketPage = () => {
|
||||
return (
|
||||
<TradeGrid
|
||||
market={data}
|
||||
onSelect={onSelect}
|
||||
pinnedAsset={
|
||||
data?.tradableInstrument.instrument.product.settlementAsset
|
||||
}
|
||||
@@ -93,12 +97,11 @@ export const MarketPage = () => {
|
||||
return (
|
||||
<TradePanels
|
||||
market={data}
|
||||
pinnedAsset={
|
||||
data?.tradableInstrument.instrument.product.settlementAsset
|
||||
}
|
||||
onSelect={onSelect}
|
||||
onClickCollateral={() => navigate('/portfolio')}
|
||||
/>
|
||||
);
|
||||
}, [largeScreen, data]);
|
||||
}, [largeScreen, data, onSelect, navigate]);
|
||||
|
||||
if (!data && marketId) {
|
||||
return (
|
||||
|
||||
@@ -9,6 +9,7 @@ import { OracleBanner } from '@vegaprotocol/markets';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import {
|
||||
ResizableGrid,
|
||||
ResizableGridPanel,
|
||||
@@ -23,6 +24,7 @@ import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
interface TradeGridProps {
|
||||
market: Market | null;
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}
|
||||
|
||||
@@ -38,6 +40,7 @@ const MainGrid = memo(
|
||||
const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({
|
||||
id: 'middle-1',
|
||||
});
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
|
||||
return (
|
||||
<ResizableGrid vertical onChange={handleOnLayoutChange}>
|
||||
@@ -95,27 +98,30 @@ const MainGrid = memo(
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-bottom">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<TradingViews.positions.component />
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
id="open-orders"
|
||||
name={t('Open')}
|
||||
menu={<TradingViews.activeOrders.menu marketId={marketId} />}
|
||||
>
|
||||
<TradingViews.orders.component filter={Filter.Open} />
|
||||
<Tab id="open-orders" name={t('Open')}>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="closed-orders" name={t('Closed')}>
|
||||
<TradingViews.orders.component filter={Filter.Closed} />
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||
<TradingViews.orders.component filter={Filter.Rejected} />
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
id="orders"
|
||||
name={t('All')}
|
||||
menu={<TradingViews.orders.menu marketId={marketId} />}
|
||||
>
|
||||
<TradingViews.orders.component />
|
||||
<Tab id="orders" name={t('All')}>
|
||||
<TradingViews.orders.component marketId={marketId} />
|
||||
</Tab>
|
||||
{FLAGS.STOP_ORDERS ? (
|
||||
<Tab id="stop-orders" name={t('Stop orders')}>
|
||||
@@ -123,14 +129,17 @@ const MainGrid = memo(
|
||||
</Tab>
|
||||
) : null}
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<TradingViews.fills.component marketId={marketId} />
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab
|
||||
id="accounts"
|
||||
name={t('Collateral')}
|
||||
menu={<TradingViews.collateral.menu />}
|
||||
>
|
||||
<TradingViews.collateral.component pinnedAsset={pinnedAsset} />
|
||||
<Tab id="accounts" name={t('Collateral')}>
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
onMarketClick={onMarketClick}
|
||||
hideButtons
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { OracleBanner } from '@vegaprotocol/markets';
|
||||
import {
|
||||
useMarketClickHandler,
|
||||
useMarketLiquidityClickHandler,
|
||||
} from '../../lib/hooks/use-market-click-handler';
|
||||
import type { TradingView } from './trade-views';
|
||||
import { TradingViews } from './trade-views';
|
||||
import { useState } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { NO_MARKET } from './constants';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
@@ -16,14 +20,33 @@ import { FLAGS } from '@vegaprotocol/environment';
|
||||
|
||||
interface TradePanelsProps {
|
||||
market: Market | null;
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
onOrderTypeClick?: (marketId: string) => void;
|
||||
onClickCollateral: () => void;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}
|
||||
|
||||
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
export const TradePanels = ({
|
||||
market,
|
||||
onSelect,
|
||||
onClickCollateral,
|
||||
pinnedAsset,
|
||||
}: TradePanelsProps) => {
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
|
||||
const [view, setView] = useState<TradingView>('candles');
|
||||
|
||||
const renderView = () => {
|
||||
const Component = TradingViews[view].component;
|
||||
const Component = memo<{
|
||||
marketId: string;
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
onOrderTypeClick?: (marketId: string) => void;
|
||||
onClickCollateral: () => void;
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}>(TradingViews[view].component);
|
||||
|
||||
if (!Component) {
|
||||
throw new Error(`No component for view: ${view}`);
|
||||
@@ -31,7 +54,16 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
|
||||
if (!market) return <Splash>{NO_MARKET}</Splash>;
|
||||
|
||||
return <Component marketId={market?.id} pinnedAsset={pinnedAsset} />;
|
||||
return (
|
||||
<Component
|
||||
marketId={market?.id}
|
||||
onSelect={onSelect}
|
||||
onClickCollateral={onClickCollateral}
|
||||
pinnedAsset={pinnedAsset}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderMenu = () => {
|
||||
@@ -39,10 +71,9 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
||||
|
||||
if ('menu' in viewCfg) {
|
||||
const Menu = viewCfg.menu;
|
||||
|
||||
return (
|
||||
<div className="flex gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
||||
<Menu marketId={market?.id || ''} />
|
||||
<Menu />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
CandlesChartContainer,
|
||||
CandlesMenu,
|
||||
} from '@vegaprotocol/candles-chart';
|
||||
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { NO_MARKET } from './constants';
|
||||
import { OrderbookContainer } from '../../components/orderbook-container';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
@@ -16,7 +16,6 @@ import { LiquidityContainer } from '../../components/liquidity-container';
|
||||
import type { OrderContainerProps } from '../../components/orders-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { StopOrdersContainer } from '../../components/stop-orders-container';
|
||||
import { AccountsMenu } from '../../components/accounts-menu';
|
||||
|
||||
type MarketDependantView =
|
||||
| typeof CandlesChartContainer
|
||||
@@ -63,7 +62,6 @@ export const TradingViews = {
|
||||
component: (props: OrderContainerProps) => (
|
||||
<OrdersContainer {...props} filter={Filter.Open} />
|
||||
),
|
||||
menu: OpenOrdersMenu,
|
||||
},
|
||||
closedOrders: {
|
||||
label: 'Closed',
|
||||
@@ -80,16 +78,11 @@ export const TradingViews = {
|
||||
orders: {
|
||||
label: 'All',
|
||||
component: OrdersContainer,
|
||||
menu: OpenOrdersMenu,
|
||||
},
|
||||
stopOrders: {
|
||||
label: 'Stop',
|
||||
component: StopOrdersContainer,
|
||||
},
|
||||
collateral: {
|
||||
label: 'Collateral',
|
||||
component: AccountsContainer,
|
||||
menu: AccountsMenu,
|
||||
},
|
||||
collateral: { label: 'Collateral', component: AccountsContainer },
|
||||
fills: { label: 'Fills', component: FillsContainer },
|
||||
};
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { DepositsTable } from '@vegaprotocol/deposits';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useSidebar, ViewType } from '../../components/sidebar';
|
||||
|
||||
export const DepositsContainer = () => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: depositsProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
return (
|
||||
<div className="h-full">
|
||||
<DepositsTable
|
||||
rowData={data}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
||||
/>
|
||||
{!isReadOnly && (
|
||||
<div className="h-auto flex justify-end p-2 bottom-0 right-0 absolute dark:bg-black/75 bg-white/75 rounded">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
data-testid="deposit-button"
|
||||
>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,11 +6,12 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { usePageTitleStore } from '../../stores';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { AccountsContainer } from '../../components/accounts-container';
|
||||
import { DepositsContainer } from '../../components/deposits-container';
|
||||
import { DepositsContainer } from './deposits-container';
|
||||
import { FillsContainer } from '../../components/fills-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import { WithdrawalsContainer } from '../../components/withdrawals-container';
|
||||
import { WithdrawalsContainer } from './withdrawals-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { LedgerContainer } from '../../components/ledger-container';
|
||||
import { AccountHistoryContainer } from './account-history-container';
|
||||
@@ -20,9 +21,6 @@ import {
|
||||
usePaneLayout,
|
||||
} from '../../components/resizable-grid';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
import { AccountsMenu } from '../../components/accounts-menu';
|
||||
import { DepositsMenu } from '../../components/deposits-menu';
|
||||
import { WithdrawalsMenu } from '../../components/withdrawals-menu';
|
||||
|
||||
const WithdrawalsIndicator = () => {
|
||||
const { ready } = useIncompleteWithdrawals();
|
||||
@@ -53,6 +51,7 @@ export const Portfolio = () => {
|
||||
}
|
||||
}, [init, view, setView]);
|
||||
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
|
||||
const wrapperClasses = 'p-0.5 h-full max-h-full flex flex-col';
|
||||
return (
|
||||
@@ -65,13 +64,13 @@ export const Portfolio = () => {
|
||||
<AccountHistoryContainer />
|
||||
</Tab>
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<PositionsContainer allKeys />
|
||||
<PositionsContainer onMarketClick={onMarketClick} allKeys />
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<OrdersContainer />
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<FillsContainer />
|
||||
<FillsContainer onMarketClick={onMarketClick} />
|
||||
</Tab>
|
||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||
<LedgerContainer />
|
||||
@@ -86,21 +85,16 @@ export const Portfolio = () => {
|
||||
>
|
||||
<PortfolioGridChild>
|
||||
<Tabs storageKey="console-portfolio-bottom">
|
||||
<Tab
|
||||
id="collateral"
|
||||
name={t('Collateral')}
|
||||
menu={<AccountsMenu />}
|
||||
>
|
||||
<Tab id="collateral" name={t('Collateral')}>
|
||||
<AccountsContainer />
|
||||
</Tab>
|
||||
<Tab id="deposits" name={t('Deposits')} menu={<DepositsMenu />}>
|
||||
<Tab id="deposits" name={t('Deposits')}>
|
||||
<DepositsContainer />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="withdrawals"
|
||||
name={t('Withdrawals')}
|
||||
indicator={<WithdrawalsIndicator />}
|
||||
menu={<WithdrawalsMenu />}
|
||||
>
|
||||
<WithdrawalsContainer />
|
||||
</Tab>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
withdrawalProvider,
|
||||
WithdrawalsTable,
|
||||
useIncompleteWithdrawals,
|
||||
} from '@vegaprotocol/withdraws';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
|
||||
export const WithdrawalsContainer = () => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: withdrawalProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
const { ready, delayed } = useIncompleteWithdrawals();
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="h-full relative">
|
||||
<WithdrawalsTable
|
||||
data-testid="withdrawals-history"
|
||||
rowData={data}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
|
||||
ready={ready}
|
||||
delayed={delayed}
|
||||
/>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<div className="h-auto flex justify-end p-2 bottom-0 right-0 absolute dark:bg-black/75 bg-white/75 rounded">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => setView({ type: ViewType.Withdraw })}
|
||||
data-testid="withdraw-dialog-button"
|
||||
>
|
||||
{t('Make withdrawal')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
@@ -11,14 +12,16 @@ import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
export const AccountsContainer = ({
|
||||
pinnedAsset,
|
||||
hideButtons,
|
||||
onMarketClick,
|
||||
}: {
|
||||
pinnedAsset?: PinnedAsset;
|
||||
hideButtons?: boolean;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}) => {
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
@@ -45,23 +48,44 @@ export const AccountsContainer = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<AccountManager
|
||||
partyId={pubKey}
|
||||
onClickAsset={onClickAsset}
|
||||
onClickWithdraw={(assetId) => {
|
||||
setView({ type: ViewType.Withdraw, assetId });
|
||||
}}
|
||||
onClickDeposit={(assetId) => {
|
||||
setView({ type: ViewType.Deposit, assetId });
|
||||
}}
|
||||
onClickTransfer={(assetId) => {
|
||||
setView({ type: ViewType.Transfer, assetId });
|
||||
}}
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
pinnedAsset={pinnedAsset}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
<div className="h-full relative">
|
||||
<AccountManager
|
||||
partyId={pubKey}
|
||||
onClickAsset={onClickAsset}
|
||||
onClickWithdraw={(assetId) => {
|
||||
setView({ type: ViewType.Withdraw, assetId });
|
||||
}}
|
||||
onClickDeposit={(assetId) => {
|
||||
setView({ type: ViewType.Deposit, assetId });
|
||||
}}
|
||||
onClickTransfer={(assetId) => {
|
||||
setView({ type: ViewType.Transfer, assetId });
|
||||
}}
|
||||
onMarketClick={onMarketClick}
|
||||
isReadOnly={isReadOnly}
|
||||
pinnedAsset={pinnedAsset}
|
||||
gridProps={gridStoreCallbacks}
|
||||
/>
|
||||
{!isReadOnly && !hideButtons && (
|
||||
<div className="flex gap-2 justify-end p-2 absolute bottom-0 right-0 dark:bg-black/75 bg-white/75 rounded">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
data-testid="open-transfer"
|
||||
onClick={() => setView({ type: ViewType.Transfer })}
|
||||
>
|
||||
{t('Transfer')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const AccountsMenu = () => {
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
data-testid="open-transfer"
|
||||
onClick={() => setView({ type: ViewType.Transfer })}
|
||||
>
|
||||
{t('Transfer')}
|
||||
</TradingButton>
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
>
|
||||
{t('Deposit')}
|
||||
</TradingButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './accounts-menu';
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { DepositsTable } from '@vegaprotocol/deposits';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
export const DepositsContainer = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: depositsProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
return (
|
||||
<DepositsTable
|
||||
rowData={data}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './deposits-container';
|
||||
@@ -1,18 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const DepositsMenu = () => {
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
data-testid="deposit-button"
|
||||
>
|
||||
{t('Deposit')}
|
||||
</TradingButton>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './deposits-menu';
|
||||
@@ -7,10 +7,14 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
export const FillsContainer = ({ marketId }: { marketId?: string }) => {
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
export const FillsContainer = ({
|
||||
marketId,
|
||||
onMarketClick,
|
||||
}: {
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
const gridStore = useFillsStore((store) => store.gridStore);
|
||||
|
||||
@@ -134,20 +134,6 @@ describe('MarketSelector', () => {
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
it('Button "All" should be selected by default', () => {
|
||||
const buttons = ['All', 'Futures', 'Spot', 'Perpetuals'];
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" onSelect={jest.fn()} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
screen
|
||||
.getAllByTestId(/^product-(All|Future|Spot|Perpetual)$/)
|
||||
.forEach((elem, i) => {
|
||||
expect(elem.textContent).toEqual(buttons[i]);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders only active markets', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
@@ -183,12 +169,6 @@ describe('MarketSelector', () => {
|
||||
activeMarkets.length
|
||||
);
|
||||
expect(screen.queryByTestId('no-items')).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByTestId('product-All'));
|
||||
expect(screen.queryAllByTestId(/market-\d/)).toHaveLength(
|
||||
activeMarkets.length
|
||||
);
|
||||
expect(screen.queryByTestId('no-items')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('filters by search term', async () => {
|
||||
|
||||
@@ -39,7 +39,7 @@ export const MarketSelector = ({
|
||||
}) => {
|
||||
const [filter, setFilter] = useState<Filter>({
|
||||
searchTerm: '',
|
||||
product: Product.All,
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
// Make sure these match the available __typename properties on product
|
||||
export const Product = {
|
||||
All: 'All',
|
||||
Future: 'Future',
|
||||
Spot: 'Spot',
|
||||
Perpetual: 'Perpetual',
|
||||
@@ -17,7 +16,6 @@ export type ProductType = keyof typeof Product;
|
||||
const ProductTypeMapping: {
|
||||
[key in ProductType]: string;
|
||||
} = {
|
||||
[Product.All]: 'All',
|
||||
[Product.Future]: 'Futures',
|
||||
[Product.Spot]: 'Spot',
|
||||
[Product.Perpetual]: 'Perpetuals',
|
||||
@@ -51,12 +49,8 @@ export const ProductSelector = ({
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<Link
|
||||
to={Routes.MARKETS}
|
||||
className="flex items-center gap-2 ml-auto"
|
||||
title={t('See all markets')}
|
||||
>
|
||||
<span className="underline underline-offset-4">{t('Browse')}</span>
|
||||
<Link to={Routes.MARKETS} className="flex items-center gap-2 ml-auto">
|
||||
<span className="underline underline-offset-4">{t('All markets')}</span>
|
||||
<VegaIcon name={VegaIconNames.ARROW_RIGHT} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -120,13 +120,6 @@ describe('useMarketSelectorList', () => {
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[2]]);
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.All,
|
||||
sort: Sort.None,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual(markets);
|
||||
});
|
||||
|
||||
it('filters by asset', () => {
|
||||
|
||||
@@ -5,7 +5,6 @@ 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';
|
||||
import { Product } from './product-selector';
|
||||
|
||||
// Used for sort order and filter
|
||||
const MARKET_TEMPLATE = [
|
||||
@@ -29,10 +28,7 @@ export const useMarketSelectorList = ({
|
||||
.filter((m) => isMarketActive(m.state))
|
||||
// only selected product type
|
||||
.filter((m) => {
|
||||
if (
|
||||
product === Product.All ||
|
||||
m.tradableInstrument.instrument.product.__typename === product
|
||||
) {
|
||||
if (m.tradableInstrument.instrument.product.__typename === product) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -25,10 +25,11 @@ export const FilterStatusValue = {
|
||||
};
|
||||
|
||||
export interface OrderContainerProps {
|
||||
marketId?: string;
|
||||
filter?: Filter;
|
||||
}
|
||||
|
||||
export const OrdersContainer = ({ filter }: OrderContainerProps) => {
|
||||
export const OrdersContainer = ({ marketId, filter }: OrderContainerProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
const onOrderTypeClick = useMarketLiquidityClickHandler();
|
||||
@@ -44,6 +45,7 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
|
||||
return (
|
||||
<OrderListManager
|
||||
partyId={pubKey}
|
||||
marketId={marketId}
|
||||
filter={filter}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
|
||||
@@ -7,10 +7,14 @@ import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
|
||||
export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
|
||||
const onMarketClick = useMarketClickHandler(true);
|
||||
export const PositionsContainer = ({
|
||||
onMarketClick,
|
||||
allKeys,
|
||||
}: {
|
||||
onMarketClick?: (marketId: string) => void;
|
||||
allKeys?: boolean;
|
||||
}) => {
|
||||
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
|
||||
|
||||
const gridStore = usePositionsStore((store) => store.gridStore);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './withdrawals-container';
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
withdrawalProvider,
|
||||
WithdrawalsTable,
|
||||
useIncompleteWithdrawals,
|
||||
} from '@vegaprotocol/withdraws';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
|
||||
export const WithdrawalsContainer = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: withdrawalProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
});
|
||||
const { ready, delayed } = useIncompleteWithdrawals();
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
return (
|
||||
<WithdrawalsTable
|
||||
data-testid="withdrawals-history"
|
||||
rowData={data}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
|
||||
ready={ready}
|
||||
delayed={delayed}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './withdrawals-menu';
|
||||
@@ -1,18 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const WithdrawalsMenu = () => {
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Withdraw })}
|
||||
data-testid="withdraw-dialog-button"
|
||||
>
|
||||
{t('Make withdrawal')}
|
||||
</TradingButton>
|
||||
);
|
||||
};
|
||||
@@ -43,6 +43,7 @@ import { Navbar } from '../components/navbar';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
ProtocolUpgradeCountdownMode,
|
||||
ProtocolUpgradeInProgressNotification,
|
||||
ProtocolUpgradeProposalNotification,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { ViewingBanner } from '../components/viewing-banner';
|
||||
@@ -111,6 +112,7 @@ function AppBody({ Component }: AppProps) {
|
||||
<ProtocolUpgradeProposalNotification
|
||||
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
|
||||
/>
|
||||
<ProtocolUpgradeInProgressNotification />
|
||||
<ViewingBanner />
|
||||
<UpgradeBanner showVersionChange={true} />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Head from 'next/head';
|
||||
import { ClientRouter } from './client-router';
|
||||
|
||||
/**
|
||||
@@ -7,30 +6,5 @@ import { ClientRouter } from './client-router';
|
||||
* have to serve a static site via next export
|
||||
*/
|
||||
export default function Index() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta charSet="utf-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="https://static.vega.xyz/favicon.ico"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Vega Protocol - Console" />
|
||||
|
||||
<link rel="apple-touch-icon" href="assets/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="assets/manifest.json" />
|
||||
<link
|
||||
rel="preload"
|
||||
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
/>
|
||||
<title>VEGA Console dApp</title>
|
||||
</Head>
|
||||
<ClientRouter />
|
||||
</>
|
||||
);
|
||||
return <ClientRouter />;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,21 @@ import {
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { Transfer } from '@vegaprotocol/wallet';
|
||||
import { useVegaTransactionStore, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
useVegaTransactionStore,
|
||||
useVegaWallet,
|
||||
useVegaWalletDialogStore,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { accountsDataProvider } from './accounts-data-provider';
|
||||
import { TransferForm } from './transfer-form';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import { Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
Lozenge,
|
||||
Notification,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
const { pubKey, pubKeys } = useVegaWallet();
|
||||
@@ -23,6 +32,10 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
|
||||
const create = useVegaTransactionStore((store) => store.create);
|
||||
|
||||
const transfer = useCallback(
|
||||
@@ -61,6 +74,28 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
)}
|
||||
{t('. If you are at all unsure, stop and seek advice.')}
|
||||
</p>
|
||||
{!pubKey && (
|
||||
<div className="mb-4">
|
||||
<Notification
|
||||
intent={Intent.Warning}
|
||||
message={
|
||||
<p className="text-sm pb-2">
|
||||
You need a{' '}
|
||||
<ExternalLink href="https://vega.xyz/wallet">
|
||||
Vega wallet
|
||||
</ExternalLink>{' '}
|
||||
to make a transfer.
|
||||
</p>
|
||||
}
|
||||
buttonProps={{
|
||||
text: t('Connect wallet'),
|
||||
action: openVegaWalletDialog,
|
||||
dataTestId: 'order-connect-wallet',
|
||||
size: 'small',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<TransferForm
|
||||
pubKey={pubKey}
|
||||
pubKeys={pubKeys ? pubKeys?.map((pk) => pk.publicKey) : null}
|
||||
|
||||
@@ -121,9 +121,12 @@ export function createClient({
|
||||
// called on startup, NodeCheck and NodeCheckTimeUpdate are called
|
||||
// by the NodeSwitcher component and the useNodeHealth hook
|
||||
if (
|
||||
['NodeGuard', 'NodeCheck', 'NodeCheckTimeUpdate'].includes(
|
||||
operation.operationName
|
||||
)
|
||||
[
|
||||
'NodeGuard',
|
||||
'NodeCheck',
|
||||
'NodeCheckTimeUpdate',
|
||||
'BlockStatistics',
|
||||
].includes(operation.operationName)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Intent, Notification } from '@vegaprotocol/ui-toolkit';
|
||||
import { Intent, Notification, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
interface ZeroBalanceErrorProps {
|
||||
@@ -6,11 +6,13 @@ interface ZeroBalanceErrorProps {
|
||||
id: string;
|
||||
symbol: string;
|
||||
};
|
||||
onClickCollateral?: () => void;
|
||||
onDeposit: (assetId: string) => void;
|
||||
}
|
||||
|
||||
export const ZeroBalanceError = ({
|
||||
asset,
|
||||
onClickCollateral,
|
||||
onDeposit,
|
||||
}: ZeroBalanceErrorProps) => {
|
||||
return (
|
||||
@@ -23,6 +25,12 @@ export const ZeroBalanceError = ({
|
||||
'You need %s in your wallet to trade in this market. ',
|
||||
asset.symbol
|
||||
)}
|
||||
{onClickCollateral && (
|
||||
<>
|
||||
{t('See all your')}{' '}
|
||||
<Link onClick={onClickCollateral}>collateral</Link>.
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
buttonProps={{
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
useMarket,
|
||||
useMarketPrice,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { AsyncRendererInline } from '@vegaprotocol/ui-toolkit';
|
||||
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
@@ -17,6 +17,7 @@ import { FLAGS } from '@vegaprotocol/environment';
|
||||
interface DealTicketContainerProps {
|
||||
marketId: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onClickCollateral?: () => void;
|
||||
onDeposit: (assetId: string) => void;
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ export const DealTicketContainer = ({
|
||||
const { data: marketPrice } = useMarketPrice(market?.id);
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
return (
|
||||
<AsyncRendererInline
|
||||
<AsyncRenderer
|
||||
data={market && marketData}
|
||||
loading={marketLoading || marketDataLoading}
|
||||
error={marketError || marketDataError}
|
||||
@@ -65,8 +66,10 @@ export const DealTicketContainer = ({
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<p>{t('Could not load market')}</p>
|
||||
<Splash>
|
||||
<p>{t('Could not load market')}</p>
|
||||
</Splash>
|
||||
)}
|
||||
</AsyncRendererInline>
|
||||
</AsyncRenderer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -74,6 +74,7 @@ export interface DealTicketProps {
|
||||
marketPrice?: string | null;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
submit: (order: OrderSubmission) => void;
|
||||
onClickCollateral?: () => void;
|
||||
onDeposit: (assetId: string) => void;
|
||||
}
|
||||
|
||||
@@ -120,6 +121,7 @@ export const DealTicket = ({
|
||||
marketData,
|
||||
marketPrice,
|
||||
submit,
|
||||
onClickCollateral,
|
||||
onDeposit,
|
||||
}: DealTicketProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
@@ -528,6 +530,7 @@ export const DealTicket = ({
|
||||
}
|
||||
isReadOnly={isReadOnly}
|
||||
pubKey={pubKey}
|
||||
onClickCollateral={onClickCollateral}
|
||||
onDeposit={onDeposit}
|
||||
/>
|
||||
<DealTicketButton side={side} />
|
||||
@@ -563,6 +566,7 @@ interface SummaryMessageProps {
|
||||
margin: string;
|
||||
isReadOnly: boolean;
|
||||
pubKey: string | null;
|
||||
onClickCollateral?: () => void;
|
||||
onDeposit: (assetId: string) => void;
|
||||
}
|
||||
|
||||
@@ -592,6 +596,7 @@ const SummaryMessage = memo(
|
||||
margin,
|
||||
isReadOnly,
|
||||
pubKey,
|
||||
onClickCollateral,
|
||||
onDeposit,
|
||||
}: SummaryMessageProps) => {
|
||||
// Specific error UI for if balance is so we can
|
||||
@@ -603,7 +608,11 @@ const SummaryMessage = memo(
|
||||
if (error?.type === SummaryValidationType.NoCollateral) {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<ZeroBalanceError asset={asset} onDeposit={onDeposit} />
|
||||
<ZeroBalanceError
|
||||
asset={asset}
|
||||
onClickCollateral={onClickCollateral}
|
||||
onDeposit={onDeposit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const EmptyLinks: DAppLinks = {
|
||||
|
||||
const ExplorerLinks = {
|
||||
...EmptyLinks,
|
||||
[Networks.STAGNET1]: 'https://explorer.stagnet1.vega.rocks',
|
||||
[Networks.TESTNET]: 'https://explorer.fairground.wtf',
|
||||
[Networks.VALIDATOR_TESTNET]:
|
||||
'https://explorer.validators-testnet.vega.rocks',
|
||||
@@ -40,17 +41,18 @@ const ConsoleLinks = {
|
||||
...EmptyLinks,
|
||||
[Networks.STAGNET1]: 'https://trading.stagnet1.vega.rocks',
|
||||
[Networks.TESTNET]: 'https://console.fairground.wtf',
|
||||
[Networks.MAINNET]: 'https://console.vega.xyz',
|
||||
[Networks.MAINNET_MIRROR]: 'https://console.mainnet-mirror.vega.rocks/',
|
||||
[Networks.MAINNET]: 'https://vega.trading',
|
||||
[Networks.MAINNET_MIRROR]: 'https://console.mainnet-mirror.vega.rocks',
|
||||
};
|
||||
|
||||
const TokenLinks = {
|
||||
...EmptyLinks,
|
||||
[Networks.DEVNET]: 'https://dev.governance.vega.xyz',
|
||||
[Networks.STAGNET1]: 'https://governance.stagnet1.vega.rocks',
|
||||
[Networks.TESTNET]: 'https://governance.fairground.wtf',
|
||||
[Networks.VALIDATOR_TESTNET]:
|
||||
'https://governance.validators-testnet.vega.rocks',
|
||||
[Networks.MAINNET_MIRROR]: 'https://governance.mainnet-mirror.vega.rocks/',
|
||||
[Networks.MAINNET_MIRROR]: 'https://governance.mainnet-mirror.vega.rocks',
|
||||
[Networks.MAINNET]: 'https://governance.vega.xyz',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './open-orders-menu';
|
||||
export * from './order-data-provider';
|
||||
export * from './order-list';
|
||||
export * from './order-list-manager';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './open-orders-menu';
|
||||
@@ -1,39 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaTransactionStore, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useHasAmendableOrder } from '../../order-hooks';
|
||||
|
||||
export const OpenOrdersMenu = ({ marketId }: { marketId: string }) => {
|
||||
const { isReadOnly } = useVegaWallet();
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
const hasAmendableOrder = useHasAmendableOrder(marketId);
|
||||
|
||||
if (isReadOnly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!hasAmendableOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CancelAllOrdersButton
|
||||
onClick={() => {
|
||||
create({
|
||||
orderCancellation: {},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={onClick}
|
||||
data-testid="cancelAll"
|
||||
>
|
||||
{t('Cancel all')}
|
||||
</TradingButton>
|
||||
);
|
||||
@@ -1,7 +1,9 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { OrderListTable } from '../order-list/order-list';
|
||||
import { useHasAmendableOrder } from '../../order-hooks/use-has-amendable-order';
|
||||
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { ordersWithMarketProvider } from '../order-data-provider/order-data-provider';
|
||||
@@ -22,6 +24,7 @@ export enum Filter {
|
||||
|
||||
export interface OrderListManagerProps {
|
||||
partyId: string;
|
||||
marketId?: string;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
isReadOnly: boolean;
|
||||
@@ -31,6 +34,7 @@ export interface OrderListManagerProps {
|
||||
|
||||
export const OrderListManager = ({
|
||||
partyId,
|
||||
marketId,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
isReadOnly,
|
||||
@@ -41,6 +45,7 @@ export const OrderListManager = ({
|
||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||
const [viewOrder, setViewOrder] = useState<Order | null>(null);
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
const hasAmendableOrder = useHasAmendableOrder(marketId);
|
||||
const variables =
|
||||
filter === Filter.Open
|
||||
? { partyId, filter: { liveOnly: true } }
|
||||
@@ -71,6 +76,12 @@ export const OrderListManager = ({
|
||||
[create]
|
||||
);
|
||||
|
||||
const cancelAll = useCallback(() => {
|
||||
create({
|
||||
orderCancellation: {},
|
||||
});
|
||||
}, [create]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-full relative">
|
||||
@@ -89,6 +100,9 @@ export const OrderListManager = ({
|
||||
{...gridProps}
|
||||
/>
|
||||
</div>
|
||||
{!isReadOnly && hasAmendableOrder && (
|
||||
<CancelAllOrdersButton onClick={cancelAll} />
|
||||
)}
|
||||
{editOrder && (
|
||||
<OrderEditDialog
|
||||
isOpen={Boolean(editOrder)}
|
||||
@@ -134,3 +148,16 @@ export const OrderListManager = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const CancelAllOrdersButton = ({ onClick }: { onClick: () => void }) => (
|
||||
<div className="dark:bg-black/75 bg-white/75 h-auto flex justify-end p-2 absolute bottom-0 right-0 rounded">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={onClick}
|
||||
data-testid="cancelAll"
|
||||
>
|
||||
{t('Cancel all')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
import type { Order } from '../order-data-provider';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface OrderViewDialogProps {
|
||||
isOpen: boolean;
|
||||
@@ -51,21 +50,6 @@ export const OrderViewDialog = ({
|
||||
)}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow key={'order-side'}>
|
||||
<div data-testid={'order-side-label'}>{t('Side')}</div>
|
||||
<div
|
||||
data-testid={`order-side-value`}
|
||||
className={classNames({
|
||||
// BUY
|
||||
'text-market-green-600 dark:text-market-green':
|
||||
order.side === Schema.Side.SIDE_BUY,
|
||||
// SELL
|
||||
'text-market-red': order.side === Schema.Side.SIDE_SELL,
|
||||
})}
|
||||
>
|
||||
{Schema.SideMapping[order.side as Schema.Side]}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow key={'order-type'}>
|
||||
<div data-testid={'order-type-label'}>{t('Type')}</div>
|
||||
<div data-testid={`order-type-value`}>
|
||||
@@ -239,6 +223,7 @@ export const OrderViewDialog = ({
|
||||
<div data-testid={`order-iceberg-order-peak-size-value`}>
|
||||
<Size
|
||||
value={order.icebergOrder.peakSize}
|
||||
side={order.side}
|
||||
positionDecimalPlaces={
|
||||
order.market?.positionDecimalPlaces as number
|
||||
}
|
||||
@@ -263,6 +248,7 @@ export const OrderViewDialog = ({
|
||||
<div data-testid={`order-iceberg-order-minimum-visible-size-value`}>
|
||||
<Size
|
||||
value={order.icebergOrder.minimumVisibleSize}
|
||||
side={order.side}
|
||||
positionDecimalPlaces={
|
||||
order.market?.positionDecimalPlaces as number
|
||||
}
|
||||
@@ -281,6 +267,7 @@ export const OrderViewDialog = ({
|
||||
<div data-testid={`order-iceberg-order-reserved-remaining-value`}>
|
||||
<Size
|
||||
value={order.icebergOrder.reservedRemaining}
|
||||
side={order.side}
|
||||
positionDecimalPlaces={
|
||||
order.market?.positionDecimalPlaces as number
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './asset-proposal-notification';
|
||||
export * from './market-proposal-notification';
|
||||
export * from './protocol-upgrade-countdown';
|
||||
export * from './protocol-upgrade-proposal-notification';
|
||||
export * from './proposals-list';
|
||||
export * from './protocol-upgrade-countdown';
|
||||
export * from './protocol-upgrade-in-progress-notification';
|
||||
export * from './protocol-upgrade-proposal-notification';
|
||||
export * from './voting-progress';
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useProtocolUpgradeProposalLink } from '@vegaprotocol/environment';
|
||||
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
ExternalLink,
|
||||
Intent,
|
||||
NotificationBanner,
|
||||
SHORT,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { StoredNextProtocolUpgradeData } from '../lib';
|
||||
import {
|
||||
NEXT_PROTOCOL_UPGRADE_PROPOSAL_SNAPSHOT,
|
||||
useNextProtocolUpgradeProposal,
|
||||
} from '../lib';
|
||||
import { useLocalStorageSnapshot } from '@vegaprotocol/react-helpers';
|
||||
import { useBlockRising } from '../lib/protocol-upgrade-proposals/use-block-rising';
|
||||
|
||||
/**
|
||||
* A flag determining whether to get the upgrade proposal data from local
|
||||
* storage if `useNextProtocolUpgradeProposal` fails.
|
||||
*/
|
||||
const ALLOW_STORED_PROPOSAL_DATA = true;
|
||||
|
||||
export const ProtocolUpgradeInProgressNotification = () => {
|
||||
const { data, error } = useNextProtocolUpgradeProposal(undefined, true);
|
||||
const [nextUpgrade] = useLocalStorageSnapshot(
|
||||
NEXT_PROTOCOL_UPGRADE_PROPOSAL_SNAPSHOT
|
||||
);
|
||||
const { blocksRising, block } = useBlockRising();
|
||||
const detailsLink = useProtocolUpgradeProposalLink();
|
||||
|
||||
let vegaReleaseTag: string | undefined;
|
||||
let upgradeBlockHeight: string | undefined;
|
||||
|
||||
if (error && !data && nextUpgrade && ALLOW_STORED_PROPOSAL_DATA) {
|
||||
try {
|
||||
const stored = JSON.parse(nextUpgrade) as StoredNextProtocolUpgradeData;
|
||||
vegaReleaseTag = stored.vegaReleaseTag;
|
||||
upgradeBlockHeight = stored.upgradeBlockHeight;
|
||||
} catch {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If upgrade is in progress then none of the nodes should produce blocks,
|
||||
* same should be with the tendermint block info otherwise it's a network
|
||||
* issue and not an upgrade. The upgrade usually lasts for couple of minutes.
|
||||
*
|
||||
* Once the networks is back then the notification disappears.
|
||||
*/
|
||||
const upgradeInProgress =
|
||||
vegaReleaseTag &&
|
||||
upgradeBlockHeight &&
|
||||
!blocksRising &&
|
||||
block <= Number(upgradeBlockHeight);
|
||||
|
||||
if (!upgradeInProgress) return null;
|
||||
|
||||
return (
|
||||
<NotificationBanner intent={Intent.Danger} className={SHORT}>
|
||||
<div className="uppercase">
|
||||
{t('The network is being upgraded to %s', vegaReleaseTag)}
|
||||
</div>
|
||||
<div>
|
||||
{t(
|
||||
'Trading and other network activity has stopped until the upgrade is complete.'
|
||||
)}{' '}
|
||||
{vegaReleaseTag && (
|
||||
<ExternalLink href={detailsLink(vegaReleaseTag)}>
|
||||
{t('View details')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</div>
|
||||
</NotificationBanner>
|
||||
);
|
||||
};
|
||||
@@ -29,27 +29,32 @@ export const ProtocolUpgradeProposalNotification = ({
|
||||
|
||||
const { vegaReleaseTag, upgradeBlockHeight } = data;
|
||||
|
||||
let timeLeft = time;
|
||||
let blocksLeft = Number(upgradeBlockHeight) - Number(lastBlockHeight);
|
||||
if (blocksLeft < 0) {
|
||||
blocksLeft = 0;
|
||||
timeLeft = 0;
|
||||
}
|
||||
|
||||
let countdown;
|
||||
switch (mode) {
|
||||
case ProtocolUpgradeCountdownMode.IN_BLOCKS:
|
||||
countdown = (
|
||||
<>
|
||||
<span className="text-vega-orange-500">
|
||||
{Number(upgradeBlockHeight) - Number(lastBlockHeight)}
|
||||
</span>{' '}
|
||||
<span className="text-vega-orange-500">{blocksLeft}</span>{' '}
|
||||
{t('blocks')}
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING:
|
||||
countdown =
|
||||
time !== undefined ? (
|
||||
timeLeft !== undefined ? (
|
||||
<span
|
||||
title={t('estimated time to protocol upgrade')}
|
||||
className="text-vega-orange-500"
|
||||
data-testid="upgrade-proposal-estimate"
|
||||
>
|
||||
{convertToCountdownString(time, '0:00:00:00')}
|
||||
{convertToCountdownString(timeLeft, '0:00:00:00')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-vega-orange-600 lowercase italic">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './__generated__/ProtocolUpgradeProposals';
|
||||
export * from './use-block-rising';
|
||||
export * from './use-next-protocol-upgrade-proposals';
|
||||
export * from './use-time-to-upgrade';
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { createClient } from '@vegaprotocol/apollo-client';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useBlockInfo } from '@vegaprotocol/tendermint';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import type { BlockStatisticsQuery } from './__generated__/BlockStatistics';
|
||||
import { BlockStatisticsDocument } from './__generated__/BlockStatistics';
|
||||
import compact from 'lodash/compact';
|
||||
import max from 'lodash/max';
|
||||
|
||||
const CHECK_INTERVAL = 5000; // ms
|
||||
|
||||
/**
|
||||
* Allows stale block height for given number of times. If stale count is
|
||||
* greater than this number then it is marked as not rising (producing blocks).
|
||||
*/
|
||||
const ALLOW_STALE = 2; // times -> MAX(this, 1) * CHECK_INTERVAL ~> min check time
|
||||
|
||||
export const useBlockRising = () => {
|
||||
const [blocksRising, setBlocksRising] = useState(true);
|
||||
const [block, setBlock] = useState(0);
|
||||
const nodes = useEnvironment((state) => state.nodes);
|
||||
const clients = useMemo(() => {
|
||||
return nodes.map(
|
||||
(n) =>
|
||||
n &&
|
||||
n.length > 0 &&
|
||||
createClient({
|
||||
url: n,
|
||||
cacheConfig: undefined,
|
||||
retry: false,
|
||||
connectToDevTools: false,
|
||||
connectToHeaderStore: true,
|
||||
})
|
||||
);
|
||||
}, [nodes]);
|
||||
|
||||
const { refetch: fetchBlockInfo } = useBlockInfo();
|
||||
useEffect(() => {
|
||||
let stale = 0;
|
||||
let prev = 0;
|
||||
const check = async () => {
|
||||
const queries = clients.map((client, index) =>
|
||||
client
|
||||
? client
|
||||
.query<BlockStatisticsQuery>({
|
||||
query: BlockStatisticsDocument,
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'ignore',
|
||||
})
|
||||
.catch((err) =>
|
||||
Promise.reject(
|
||||
`could not retrieve statistics from ${nodes[index]}`
|
||||
)
|
||||
)
|
||||
: undefined
|
||||
);
|
||||
|
||||
const blockInfo = await fetchBlockInfo();
|
||||
const results = (await Promise.allSettled(compact(queries))).map(
|
||||
(res) => {
|
||||
if (res && res.status === 'fulfilled') {
|
||||
return res.value.data.statistics;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const heights = compact([
|
||||
...results.map((r) => r?.blockHeight),
|
||||
blockInfo?.result.block.header.height,
|
||||
]);
|
||||
const current = max(heights);
|
||||
if (current && Number(current) > prev) {
|
||||
setBlock(Number(current));
|
||||
prev = Number(current);
|
||||
stale = 0;
|
||||
if (!blocksRising) setBlocksRising(true);
|
||||
} else {
|
||||
if (stale > ALLOW_STALE) setBlocksRising(false);
|
||||
stale++;
|
||||
}
|
||||
};
|
||||
const interval = setInterval(check, CHECK_INTERVAL);
|
||||
check();
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [clients, fetchBlockInfo, blocksRising, nodes]);
|
||||
|
||||
return { blocksRising, block };
|
||||
};
|
||||
+27
-2
@@ -2,6 +2,13 @@ import { useMemo, useEffect } from 'react';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { removePaginationWrapper, isTestEnv } from '@vegaprotocol/utils';
|
||||
import { useProtocolUpgradeProposalsQuery } from './__generated__/ProtocolUpgradeProposals';
|
||||
import { useLocalStorageSnapshot } from '@vegaprotocol/react-helpers';
|
||||
import pick from 'lodash/pick';
|
||||
|
||||
const POLL_INTERVAL = 5000; // ms
|
||||
|
||||
export const NEXT_PROTOCOL_UPGRADE_PROPOSAL_SNAPSHOT =
|
||||
'next_protocol_upgrade_proposal';
|
||||
|
||||
export const useNextProtocolUpgradeProposals = (since?: number) => {
|
||||
const { data, loading, error, startPolling, stopPolling } =
|
||||
@@ -22,7 +29,7 @@ export const useNextProtocolUpgradeProposals = (since?: number) => {
|
||||
}
|
||||
|
||||
if (!isTestEnv() && window.location.hostname !== 'localhost') {
|
||||
startPolling(5000);
|
||||
startPolling(POLL_INTERVAL);
|
||||
}
|
||||
}, [error, startPolling, stopPolling]);
|
||||
|
||||
@@ -51,10 +58,28 @@ export const useNextProtocolUpgradeProposals = (since?: number) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useNextProtocolUpgradeProposal = (since?: number) => {
|
||||
export type StoredNextProtocolUpgradeData = {
|
||||
vegaReleaseTag: string;
|
||||
upgradeBlockHeight: string;
|
||||
};
|
||||
export const useNextProtocolUpgradeProposal = (
|
||||
since?: number,
|
||||
persist = false
|
||||
) => {
|
||||
const { data, lastBlockHeight, loading, error } =
|
||||
useNextProtocolUpgradeProposals(since);
|
||||
|
||||
const [, setNextUpgrade] = useLocalStorageSnapshot(
|
||||
NEXT_PROTOCOL_UPGRADE_PROPOSAL_SNAPSHOT
|
||||
);
|
||||
useEffect(() => {
|
||||
if (data && persist) {
|
||||
setNextUpgrade(
|
||||
JSON.stringify(pick(data, 'upgradeBlockHeight', 'vegaReleaseTag'))
|
||||
);
|
||||
}
|
||||
}, [data, persist, setNextUpgrade]);
|
||||
|
||||
return {
|
||||
data: !data ? undefined : data[0],
|
||||
lastBlockHeight,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "tendermint",
|
||||
"name": "@vegaprotocol/tendermint",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { TendermintBlockResponse } from '../types';
|
||||
export const useBlockInfo = (blockHeight?: number, canFetch = true) => {
|
||||
const { TENDERMINT_URL } = useEnvironment();
|
||||
|
||||
const url = `${TENDERMINT_URL}/block?height=${blockHeight}`;
|
||||
const url = `${TENDERMINT_URL}/block?height=${blockHeight || ''}`;
|
||||
const canFetchData = Boolean(
|
||||
TENDERMINT_URL && blockHeight && !isNaN(blockHeight) && canFetch
|
||||
);
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Intent } from '../../utils/intent';
|
||||
import { Icon } from '../icon';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
export const SHORT = '!px-1 !py-1 min-h-fit';
|
||||
|
||||
interface NotificationBannerProps {
|
||||
intent?: Intent;
|
||||
children?: React.ReactNode;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { NotificationBanner } from '../notification-banner';
|
||||
import { NotificationBanner, SHORT } from '../notification-banner';
|
||||
import { Intent } from '../../utils/intent';
|
||||
import { TradingButton } from '../trading-button';
|
||||
|
||||
@@ -25,7 +25,7 @@ export const ViewingAsBanner = ({
|
||||
<NotificationBanner
|
||||
data-testid="view-banner"
|
||||
intent={Intent.None}
|
||||
className="!px-1 !py-1 min-h-fit"
|
||||
className={SHORT}
|
||||
>
|
||||
<div className="flex justify-between items-baseline">
|
||||
<span>
|
||||
|
||||
@@ -154,7 +154,6 @@ interface ProposalNewMarketTerms {
|
||||
auctionExtension: string;
|
||||
};
|
||||
logNormal: LogNormal;
|
||||
successor?: Successor;
|
||||
};
|
||||
};
|
||||
closingTimestamp: number;
|
||||
@@ -299,11 +298,6 @@ interface Condition {
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface Successor {
|
||||
parentMarketId: string;
|
||||
insurancePoolFraction: string;
|
||||
}
|
||||
|
||||
interface LogNormal {
|
||||
tau: number;
|
||||
riskAversionParameter: number;
|
||||
|
||||
@@ -32,8 +32,4 @@
|
||||
- **must** I can see an explanation of what "Parent market ID" using a tooltip (<a name="3003-PMAN-011" href="#3003-PMAN-011">3003-PMAN-011</a>)
|
||||
If the proposal has been enacted, I can see the status of the market i.e. opening auction, continuous trading, rejected
|
||||
|
||||
- **must** be able to see the market specification with the proposed update (<a name="3003-PMAN-012" href="#3003-PMAN-012">3003-PMAN-012</a>)
|
||||
|
||||
- **must** be a simple way to visually highlight / compare the difference with this market specification from the original markets specification (<a name="3003-PMAN-013" href="#3003-PMAN-013">3003-PMAN-013</a>)
|
||||
|
||||
back to [Propose](./3002-PROP-propose.md) for details on proposing
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
- by newest markets (opening timestamp) (<a name="6001-MARK-032" href="#6001-MARK-032">6001-MARK-032</a>)
|
||||
- **Must** be able to close and open the market selector (<a name="6001-MARK-066" href="#6001-MARK-066">6001-MARK-066</a>)
|
||||
- **Must** must change color and have + or negative suffix of the price change and change color for the sparkline (<a name="6001-MARK-067" href="#6001-MARK-067">6001-MARK-067</a>)
|
||||
- **Must** be default tab "All" where there's no filtering by product. (<a name="6001-MARK-070" href="#6001-MARK-070">6001-MARK-070</a>)
|
||||
|
||||
## All Markets
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ When looking at a list of orders, I...
|
||||
- **must** see if any execution flags (aka conditions. e.g. Post only, Reduce only) were applied to the order (<a name="7003-MORD-019" href="#7003-MORD-019">7003-MORD-019</a>)
|
||||
- **should** see "created at" [time](9001-DATA-data_display.md#time)
|
||||
- **could** see updated at (this is used by the system when an order is amended, or repriced (in pegged and LP) not sure this in needed)
|
||||
- **must** be able to see a single order view dialog with all the fields present above
|
||||
|
||||
- **should** see time priority (how many orders are before mine at this price)
|
||||
- if the order is `Active` & **not** part of a liquidity or peg shape: **must** see an option to [amend](#amend-order---price) the individual order (<a name="7003-MORD-007" href="#7003-MORD-007">7003-MORD-007</a>)
|
||||
- if the order is `Active` & is part of a liquidity or peg shape: **must** **not** see an option to amend the individual order (<a name="7003-MORD-008" href="#7003-MORD-008">7003-MORD-008</a>)
|
||||
|
||||
Reference in New Issue
Block a user