Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
761fd7ede6 | ||
|
|
de20e7a233 | ||
|
|
7ef11d12f1 | ||
|
|
bb1c8b4184 | ||
|
|
e87e1a53dc | ||
|
|
4a4b046b4b | ||
|
|
217c9dc783 | ||
|
|
49c5db6b05 | ||
|
|
7a799018b4 | ||
|
|
b6cce17583 | ||
|
|
ee143110e5 | ||
|
|
f21d0d3b53 | ||
|
|
19ad42573d | ||
|
|
d1129bd70a | ||
|
|
0ce84eba11 | ||
|
|
9d346d7846 | ||
|
|
b68136ba3f | ||
|
|
f5ad2de5a5 | ||
|
|
b3bfb90804 | ||
|
|
327ebf3496 | ||
|
|
1098accb84 | ||
|
|
52183555a9 | ||
|
|
51187029aa | ||
|
|
f7f7be59d3 | ||
|
|
49e3baf094 | ||
|
|
a575b4c502 | ||
|
|
af2e52d59c | ||
|
|
2725bef159 | ||
|
|
070f1905e2 | ||
|
|
6705eb4398 | ||
|
|
9d3fc04597 | ||
|
|
d3929b8d4a | ||
|
|
8a5579b1cc | ||
|
|
197f2e8097 | ||
|
|
8f5a2276de | ||
|
|
46513685c8 | ||
|
|
e66e96f12d | ||
|
|
31c8365812 | ||
|
|
460f534c7a | ||
|
|
24cd080dd4 | ||
|
|
f10c33748f | ||
|
|
ac163d0194 | ||
|
|
e7616e64f8 | ||
|
|
adca4600c2 | ||
|
|
a8eef1cb53 | ||
|
|
58d4bd1459 | ||
|
|
26020b5f71 | ||
|
|
16d345eb4a | ||
|
|
3b3fcab4f4 | ||
|
|
f54a629179 | ||
|
|
56f48671cc | ||
|
|
a65ee358ff | ||
|
|
7b8d814724 | ||
|
|
cd237dc491 | ||
|
|
9ad00adc5a | ||
|
|
fab055bb34 | ||
|
|
d66956d080 | ||
|
|
9848c8ffff | ||
|
|
b0e8ed62d2 | ||
|
|
289ff3b5b3 | ||
|
|
1d965187dc | ||
|
|
8bf3615001 | ||
|
|
1b77800b62 | ||
|
|
979dc2904b | ||
|
|
0fec3bcf3c | ||
|
|
4542e25ec9 | ||
|
|
acd96fbf21 | ||
|
|
96bf12d4dd | ||
|
|
47b81188d4 | ||
|
|
efada06256 | ||
|
|
311ef41138 | ||
|
|
5dd0cafeca | ||
|
|
f3d6aa1129 |
@@ -87,7 +87,7 @@ jobs:
|
||||
run: ls -alsh /home/runner/.vegacapsule/testnet/logs/
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: logs-${{ matrix.project }}
|
||||
path: /home/runner/.vegacapsule/testnet/logs
|
||||
|
||||
@@ -1,33 +1,27 @@
|
||||
context('Blocks page', { tags: '@regression' }, function () {
|
||||
before('visit token home page', function () {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
const blockNavigation = 'a[href="/blocks"]';
|
||||
beforeEach(() => {
|
||||
cy.visit('/blocks');
|
||||
});
|
||||
const blockHeight = '[data-testid="block-height"]';
|
||||
const blockTime = '[data-testid="block-time"]';
|
||||
const blockHeader = '[data-testid="block-header"]';
|
||||
const previousBlockBtn = '[data-testid="previous-block"]';
|
||||
const infiniteScrollWrapper = '[data-testid="infinite-scroll-wrapper"]';
|
||||
|
||||
beforeEach('navigate to blocks page', function () {
|
||||
cy.get(blockNavigation).click();
|
||||
});
|
||||
|
||||
it('Blocks page is displayed', function () {
|
||||
validateBlocksDisplayed();
|
||||
});
|
||||
|
||||
it('Blocks page is displayed on mobile', function () {
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(blockNavigation).click();
|
||||
cy.switchToMobile();
|
||||
validateBlocksDisplayed();
|
||||
});
|
||||
|
||||
it('Block validator page is displayed', function () {
|
||||
waitForBlocksResponse();
|
||||
cy.get(blockHeight).eq(0).click();
|
||||
cy.get(blockHeight).eq(0).find('a').click({ force: true });
|
||||
|
||||
cy.get('[data-testid="block-validator"]').should('not.be.empty');
|
||||
cy.get(blockTime).should('not.be.empty');
|
||||
//TODO: Add assertion for transactions when txs are added
|
||||
@@ -35,7 +29,7 @@ context('Blocks page', { tags: '@regression' }, function () {
|
||||
|
||||
it('Navigate to previous block', function () {
|
||||
waitForBlocksResponse();
|
||||
cy.get(blockHeight).eq(0).click();
|
||||
cy.get(blockHeight).eq(0).find('a').click({ force: true });
|
||||
cy.get(blockHeader)
|
||||
.invoke('text')
|
||||
.then(($blockHeaderTxt) => {
|
||||
|
||||
@@ -2,17 +2,14 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
before('navigate to network parameter page', function () {
|
||||
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
const networkParametersNavigation = 'a[href="/network-parameters"]';
|
||||
beforeEach(() => {
|
||||
cy.visit('/network-parameters');
|
||||
});
|
||||
|
||||
const networkParametersHeader = '[data-testid="network-param-header"]';
|
||||
const tableRows = '[data-testid="key-value-table-row"]';
|
||||
|
||||
before('navigate to network parameter page', function () {
|
||||
cy.visit('/');
|
||||
cy.get(networkParametersNavigation).click();
|
||||
});
|
||||
|
||||
it('should show network parameter heading at top of page', function () {
|
||||
cy.get(networkParametersHeader)
|
||||
.should('have.text', 'Network Parameters')
|
||||
@@ -201,55 +198,8 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to switch network parameter page - between light and dark mode', function () {
|
||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
|
||||
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||
const jsonFields = '.hljs';
|
||||
const sideMenuBackground = '.absolute';
|
||||
|
||||
// Engage dark mode if not already set
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.then((background_color) => {
|
||||
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
|
||||
cy.get(themeSwitcher).click();
|
||||
});
|
||||
|
||||
// Engage white mode
|
||||
cy.get(themeSwitcher).click();
|
||||
|
||||
// White Mode
|
||||
cy.get(networkParametersNavigation)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSideMenuBackgroundColor);
|
||||
|
||||
// Dark Mode
|
||||
cy.get(themeSwitcher).click();
|
||||
cy.get(networkParametersNavigation)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSideMenuBackgroundColor);
|
||||
});
|
||||
|
||||
it.skip('should be able to see network parameters - on mobile', function () {
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(networkParametersNavigation).click();
|
||||
it('should be able to see network parameters - on mobile', function () {
|
||||
cy.switchToMobile();
|
||||
cy.get_network_parameters().then((network_parameters) => {
|
||||
network_parameters = Object.entries(network_parameters);
|
||||
network_parameters.forEach((network_parameter) => {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n00.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_NETWORKS='{"SANDBOX":"https://sandbox.explorer.vega.xyz","STAGNET1":"https://stagnet1.token.vega.xyz", "STAGNET3":"https://stagnet3.explorer.vega.xyz","VALIDATOR_TESTNET":"https://validator-testnet.fairground.wtf","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# App configuration variables
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
|
||||
# App flags
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/tm
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=DEVNET
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
NX_TENDERMINT_URL=https://be.explorer.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.explorer.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=MAINNET
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
|
||||
@@ -2,9 +2,7 @@
|
||||
NX_TENDERMINT_URL=https://tm.be.mainnet-mirror.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mirror-network.json
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_VEGA_ENV=MIRROR
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
|
||||
|
||||
@@ -5,7 +5,6 @@ NX_VEGA_EXPLORER_URL=https://sandbox.explorer.vega.xyz
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.sandbox.vega.xyz/websocket
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
|
||||
@@ -4,7 +4,6 @@ NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
|
||||
NX_VEGA_ENV=STAGNET1
|
||||
NX_VEGA_EXPLORER_URL=https://stagnet1.explorer.vega.xyz
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_TOKEN_URL=https://stagnet1.token.vega.xyz
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
NX_TENDERMINT_URL=https://tm.n00.stagnet3.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.stagnet3.vega.xyz/websocket
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=STAGNET3
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
|
||||
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
|
||||
|
||||
@@ -3,11 +3,8 @@ 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://static.vega.xyz/assets/testnet-network.json
|
||||
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
|
||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||
NX_VEGA_NETWORKS={}
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
|
||||
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
|
||||
@@ -3,10 +3,8 @@ NX_VEGA_ENV=VALIDATOR_TESTNET
|
||||
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/validator-testnet-network.json
|
||||
NX_VEGA_URL=https://api.validators-testnet.vega.xyz/graphql
|
||||
NX_VEGA_REST=https://api.validators-testnet.vega.xyz/
|
||||
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.token.vega.xyz","STAGNET3":"https://stagnet3.token.vega.xyz","TESTNET":"https://token.fairground.wtf","MAINNET":"https://token.vega.xyz"}'
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
|
||||
NX_VEGA_GOVERNANCE_URL=https://validator-testnet.governance.vega.xyz
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.xyz
|
||||
|
||||
@@ -2,6 +2,5 @@
|
||||
NX_TENDERMINT_URL=http://localhost:26607/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26607/websocket
|
||||
NX_VEGA_ENV=CUSTOM
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_BLOCK_EXPLORER=
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -1,16 +1,20 @@
|
||||
import classnames from 'classnames';
|
||||
import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment';
|
||||
import { Nav } from './components/nav';
|
||||
import { Header } from './components/header';
|
||||
import { Main } from './components/main';
|
||||
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
|
||||
import { Footer } from './components/footer/footer';
|
||||
import { AnnouncementBanner, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
AnnouncementBanner,
|
||||
ExternalLink,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
AssetDetailsDialog,
|
||||
useAssetDetailsDialogStore,
|
||||
} from '@vegaprotocol/assets';
|
||||
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
|
||||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
|
||||
const DialogsContainer = () => {
|
||||
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
|
||||
@@ -25,35 +29,58 @@ const DialogsContainer = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function App() {
|
||||
const layoutClasses = classnames(
|
||||
'grid grid-rows-[auto_1fr_auto] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)_auto] md:grid-cols-[300px_1fr]',
|
||||
'min-h-[100vh] mx-auto my-0',
|
||||
'border-neutral-700 dark:border-neutral-300 lg:border-l lg:border-r',
|
||||
'bg-white dark:bg-black',
|
||||
'antialiased text-black dark:text-white',
|
||||
'overflow-hidden relative'
|
||||
);
|
||||
const MainnetSimAd = () => {
|
||||
const [shouldDisplayBanner, setShouldDisplayBanner] = useState<boolean>(true);
|
||||
|
||||
// Return an empty div so that the grid layout in _app.page.ts
|
||||
// renders correctly
|
||||
if (!shouldDisplayBanner) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AnnouncementBanner>
|
||||
<div className="grid grid-cols-[auto_1fr] gap-4 font-alpha calt uppercase text-center text-lg text-white">
|
||||
<button
|
||||
className="flex items-center"
|
||||
onClick={() => setShouldDisplayBanner(false)}
|
||||
>
|
||||
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
|
||||
</button>
|
||||
<div>
|
||||
<span className="pr-4">Mainnet sim 3 is coming soon!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">Learn more</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
);
|
||||
};
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<TendermintWebsocketProvider>
|
||||
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
|
||||
<AnnouncementBanner>
|
||||
<div className="font-alpha calt uppercase text-center text-lg text-white">
|
||||
<span className="pr-4">Mainnet sim 2 is live!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">
|
||||
Come help stress test the network
|
||||
</ExternalLink>
|
||||
<div
|
||||
className={classNames(
|
||||
'max-w-[1500px] min-h-[100vh]',
|
||||
'mx-auto my-0',
|
||||
'grid grid-rows-[auto_1fr_auto] grid-cols-1',
|
||||
'border-vega-light-200 dark:border-vega-dark-200 lg:border-l lg:border-r',
|
||||
'antialiased text-black dark:text-white',
|
||||
'overflow-hidden relative'
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<Header />
|
||||
<MainnetSimAd />
|
||||
</div>
|
||||
<div>
|
||||
<Main />
|
||||
</div>
|
||||
<div>
|
||||
<Footer />
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
|
||||
<div className={layoutClasses}>
|
||||
<Header />
|
||||
<Nav />
|
||||
<Main />
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
<DialogsContainer />
|
||||
</NetworkLoader>
|
||||
</TendermintWebsocketProvider>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const Footer = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-neutral-700 dark:border-neutral-300">
|
||||
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
|
||||
<div className="flex justify-between gap-2 align-middle">
|
||||
{GIT_COMMIT_HASH && (
|
||||
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
|
||||
|
||||
@@ -19,12 +19,10 @@ const renderComponent = () => (
|
||||
);
|
||||
|
||||
describe('Header', () => {
|
||||
it('should render heading', () => {
|
||||
it('should render navigation', () => {
|
||||
render(renderComponent());
|
||||
|
||||
expect(screen.getByTestId('explorer-header')).toHaveTextContent(
|
||||
'Vega Explorer'
|
||||
);
|
||||
expect(screen.getByTestId('navigation')).toHaveTextContent('Explorer');
|
||||
});
|
||||
it('should render search', () => {
|
||||
render(renderComponent());
|
||||
|
||||
@@ -1,43 +1,108 @@
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ThemeSwitcher, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
ThemeSwitcher,
|
||||
Navigation,
|
||||
NavigationList,
|
||||
NavigationItem,
|
||||
NavigationLink,
|
||||
NavigationBreakpoint,
|
||||
NavigationTrigger,
|
||||
NavigationContent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Search } from '../search';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import { useNavStore } from '../nav';
|
||||
import type { Navigable } from '../../routes/router-config';
|
||||
import routerConfig from '../../routes/router-config';
|
||||
import { useMemo } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import { Search } from '../search';
|
||||
|
||||
const routeToNavigationItem = (r: Navigable) => (
|
||||
<NavigationItem key={r.name}>
|
||||
<NavigationLink to={r.path}>{r.text}</NavigationLink>
|
||||
</NavigationItem>
|
||||
);
|
||||
|
||||
export const Header = () => {
|
||||
const [open, toggle] = useNavStore((state) => [state.open, state.toggle]);
|
||||
const headerClasses = classnames(
|
||||
'md:col-span-2',
|
||||
'grid grid-rows-2 md:grid-rows-1 grid-cols-[1fr_auto] md:grid-cols-[auto_1fr_auto] items-center',
|
||||
'p-4 gap-2 md:gap-4',
|
||||
'border-b border-neutral-700 dark:border-neutral-300 bg-black',
|
||||
'dark text-white'
|
||||
const mainItems = compact(
|
||||
[Routes.TX, Routes.BLOCKS, Routes.ORACLES, Routes.VALIDATORS].map((n) =>
|
||||
routerConfig.find((r) => r.path === n)
|
||||
)
|
||||
);
|
||||
|
||||
const groupedItems = compact(
|
||||
[
|
||||
Routes.PARTIES,
|
||||
Routes.ASSETS,
|
||||
Routes.MARKETS,
|
||||
Routes.GOVERNANCE,
|
||||
Routes.NETWORK_PARAMETERS,
|
||||
Routes.GENESIS,
|
||||
].map((n) => routerConfig.find((r) => r.path === n))
|
||||
);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
/**
|
||||
* Because the grouped items are displayed in a sub menu under an "Other" item
|
||||
* we need to determine whether any underlying item is active to highlight the
|
||||
* trigger in the same fashion as any other top-level `NavigationLink`.
|
||||
* This function checks whether the current location pathname is one of the
|
||||
* underlying NavigationLinks.
|
||||
*/
|
||||
const isOnOther = useMemo(() => {
|
||||
for (const path of groupedItems.map((r) => r.path)) {
|
||||
const matched = matchPath(`${path}/*`, pathname);
|
||||
if (matched) return true;
|
||||
}
|
||||
return false;
|
||||
}, [groupedItems, pathname]);
|
||||
|
||||
return (
|
||||
<header className={headerClasses}>
|
||||
<div className="flex h-full items-center sm:items-stretch gap-4">
|
||||
<Link to={Routes.HOME}>
|
||||
<h1
|
||||
className="text-white text-3xl font-alpha uppercase calt mb-0"
|
||||
data-testid="explorer-header"
|
||||
>
|
||||
{t('Vega Explorer')}
|
||||
</h1>
|
||||
</Link>
|
||||
<NetworkSwitcher />
|
||||
</div>
|
||||
<button
|
||||
data-testid="open-menu"
|
||||
className="md:hidden text-white"
|
||||
onClick={() => toggle()}
|
||||
<Navigation
|
||||
appName="Explorer"
|
||||
theme="system"
|
||||
breakpoints={[490, 900]}
|
||||
actions={
|
||||
<>
|
||||
<ThemeSwitcher />
|
||||
<Search />
|
||||
</>
|
||||
}
|
||||
onResize={(width, el) => {
|
||||
if (width < 1157) {
|
||||
// switch to magnifying glass trigger when width < 1157
|
||||
el.classList.remove('nav-search-full');
|
||||
el.classList.add('nav-search-compact');
|
||||
} else {
|
||||
el.classList.remove('nav-search-compact');
|
||||
el.classList.add('nav-search-full');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<NavigationList hide={[NavigationBreakpoint.Small]}>
|
||||
<NavigationItem>
|
||||
<NetworkSwitcher />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Small, NavigationBreakpoint.Narrow]}
|
||||
>
|
||||
<Icon name={open ? 'cross' : 'menu'} />
|
||||
</button>
|
||||
<Search />
|
||||
<ThemeSwitcher className="-my-4" />
|
||||
</header>
|
||||
{mainItems.map(routeToNavigationItem)}
|
||||
{groupedItems && (
|
||||
<NavigationItem>
|
||||
<NavigationTrigger isActive={Boolean(isOnOther)}>
|
||||
{t('Other')}
|
||||
</NavigationTrigger>
|
||||
<NavigationContent>
|
||||
<NavigationList>
|
||||
{groupedItems.map(routeToNavigationItem)}
|
||||
</NavigationList>
|
||||
</NavigationContent>
|
||||
</NavigationItem>
|
||||
)}
|
||||
</NavigationList>
|
||||
</Navigation>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,13 +4,14 @@ import { ENV } from '../../../config/env';
|
||||
import Hash from '../hash';
|
||||
export type ProposalLinkProps = {
|
||||
id: string;
|
||||
text?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a proposal ID, generates an external link over to
|
||||
* the Governance page for more information
|
||||
*/
|
||||
const ProposalLink = ({ id }: ProposalLinkProps) => {
|
||||
const ProposalLink = ({ id, text }: ProposalLinkProps) => {
|
||||
const { data } = useExplorerProposalQuery({
|
||||
variables: { id },
|
||||
});
|
||||
@@ -20,7 +21,7 @@ const ProposalLink = ({ id }: ProposalLinkProps) => {
|
||||
|
||||
return (
|
||||
<ExternalLink href={`${base}/proposals/${id}`}>
|
||||
<Hash text={label} />
|
||||
{text ? text : <Hash text={label} />}
|
||||
</ExternalLink>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,9 +4,8 @@ import {
|
||||
getMarketExpiryDateFormatted,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { MarketInfoNoCandlesQuery } from '@vegaprotocol/market-info';
|
||||
import type { MarketInfoWithData } from '@vegaprotocol/market-info';
|
||||
import { MarketInfoTable } from '@vegaprotocol/market-info';
|
||||
import pick from 'lodash/pick';
|
||||
import {
|
||||
MarketStateMapping,
|
||||
MarketTradingModeMapping,
|
||||
@@ -17,11 +16,7 @@ import BigNumber from 'bignumber.js';
|
||||
import { useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export const MarketDetails = ({
|
||||
market,
|
||||
}: {
|
||||
market: MarketInfoNoCandlesQuery['market'];
|
||||
}) => {
|
||||
export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
||||
const quoteUnit = market?.tradableInstrument.instrument.product.quoteName;
|
||||
const assetId = useMemo(
|
||||
() => market?.tradableInstrument.instrument.product?.settlementAsset.id,
|
||||
@@ -32,7 +27,9 @@ export const MarketDetails = ({
|
||||
if (!market) return null;
|
||||
|
||||
const keyDetails = {
|
||||
...pick(market, 'decimalPlaces', 'positionDecimalPlaces', 'tradingMode'),
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
tradingMode: market.tradingMode,
|
||||
state: MarketStateMapping[market.state],
|
||||
};
|
||||
const assetDecimals =
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './nav';
|
||||
@@ -1,181 +0,0 @@
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import type { Navigable } from '../../routes/router-config';
|
||||
import routerConfig from '../../routes/router-config';
|
||||
import classnames from 'classnames';
|
||||
import { create } from 'zustand';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import first from 'lodash/first';
|
||||
import last from 'lodash/last';
|
||||
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
||||
|
||||
type NavStore = {
|
||||
open: boolean;
|
||||
toggle: () => void;
|
||||
hide: () => void;
|
||||
};
|
||||
|
||||
export const useNavStore = create<NavStore>((set, get) => ({
|
||||
open: false,
|
||||
toggle: () => set({ open: !get().open }),
|
||||
hide: () => set({ open: false }),
|
||||
}));
|
||||
|
||||
const NavLinks = ({ links }: { links: Navigable[] }) => {
|
||||
const navLinks = links.map((r) => (
|
||||
<li key={r.name}>
|
||||
<NavLink
|
||||
to={r.path}
|
||||
className={({ isActive }) =>
|
||||
classnames(
|
||||
'block mb-2 px-2',
|
||||
'text-lg hover:bg-vega-pink dark:hover:bg-vega-yellow hover:text-white dark:hover:text-black',
|
||||
{
|
||||
'bg-vega-pink text-white dark:bg-vega-yellow dark:text-black':
|
||||
isActive,
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
{r.text}
|
||||
</NavLink>
|
||||
</li>
|
||||
));
|
||||
|
||||
return <ul className="pr-8 md:pr-0">{navLinks}</ul>;
|
||||
};
|
||||
|
||||
export const Nav = () => {
|
||||
const [open, hide] = useNavStore((state) => [state.open, state.hide]);
|
||||
const location = useLocation();
|
||||
|
||||
const navRef = useRef<HTMLElement>(null);
|
||||
const btnRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const focusable = useMemo(
|
||||
() =>
|
||||
navRef.current
|
||||
? [
|
||||
...(navRef.current.querySelectorAll(
|
||||
'a, button'
|
||||
) as NodeListOf<HTMLElement>),
|
||||
]
|
||||
: [],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[navRef.current] // do not remove `navRef.current` from deps
|
||||
);
|
||||
|
||||
const closeNav = useCallback(() => {
|
||||
hide();
|
||||
console.log(focusable);
|
||||
focusable.forEach((fe) =>
|
||||
fe.setAttribute(
|
||||
'tabindex',
|
||||
window.innerWidth > BREAKPOINT_MD ? '0' : '-1'
|
||||
)
|
||||
);
|
||||
}, [focusable, hide]);
|
||||
|
||||
// close navigation when location changes
|
||||
useEffect(() => {
|
||||
closeNav();
|
||||
}, [closeNav, location]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (open) {
|
||||
focusable.forEach((fe) => fe.setAttribute('tabindex', '0'));
|
||||
}
|
||||
|
||||
document.body.style.overflow = open ? 'hidden' : '';
|
||||
const offset =
|
||||
document.querySelector('header')?.getBoundingClientRect().top || 0;
|
||||
if (navRef.current) {
|
||||
navRef.current.style.height = `calc(100vh - ${offset}px)`;
|
||||
}
|
||||
|
||||
// focus current by default
|
||||
if (navRef.current && open) {
|
||||
(navRef.current.querySelector('a[aria-current]') as HTMLElement)?.focus();
|
||||
}
|
||||
|
||||
const closeOnEsc = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeNav();
|
||||
}
|
||||
};
|
||||
|
||||
// tabbing loop
|
||||
const focusLast = (e: FocusEvent) => {
|
||||
e.preventDefault();
|
||||
const isNavElement =
|
||||
e.relatedTarget && navRef.current?.contains(e.relatedTarget as Node);
|
||||
if (!isNavElement && open) {
|
||||
last(focusable)?.focus();
|
||||
}
|
||||
};
|
||||
const focusFirst = (e: FocusEvent) => {
|
||||
e.preventDefault();
|
||||
const isNavElement =
|
||||
e.relatedTarget && navRef.current?.contains(e.relatedTarget as Node);
|
||||
if (!isNavElement && open) {
|
||||
first(focusable)?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const resetOnDesktop = () => {
|
||||
focusable.forEach((fe) =>
|
||||
fe.setAttribute(
|
||||
'tabindex',
|
||||
window.innerWidth > BREAKPOINT_MD ? '0' : '-1'
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', resetOnDesktop);
|
||||
|
||||
first(focusable)?.addEventListener('focusout', focusLast);
|
||||
last(focusable)?.addEventListener('focusout', focusFirst);
|
||||
|
||||
document.addEventListener('keydown', closeOnEsc);
|
||||
return () => {
|
||||
window.removeEventListener('resize', resetOnDesktop);
|
||||
document.removeEventListener('keydown', closeOnEsc);
|
||||
first(focusable)?.removeEventListener('focusout', focusLast);
|
||||
last(focusable)?.removeEventListener('focusout', focusFirst);
|
||||
};
|
||||
}, [closeNav, focusable, open]);
|
||||
|
||||
return (
|
||||
<nav
|
||||
ref={navRef}
|
||||
className={classnames(
|
||||
'absolute top-0 z-20 overflow-y-auto',
|
||||
'transition-[right]',
|
||||
{
|
||||
'right-[-200vw] h-full': !open,
|
||||
'right-0 h-[100vh]': open,
|
||||
},
|
||||
'w-full p-4 border-neutral-700 dark:border-neutral-300',
|
||||
'bg-white dark:bg-black',
|
||||
'md:static md:border-r'
|
||||
)}
|
||||
>
|
||||
<NavLinks links={routerConfig} />
|
||||
<button
|
||||
ref={btnRef}
|
||||
className="absolute top-0 right-0 p-4 md:hidden"
|
||||
onClick={() => {
|
||||
closeNav();
|
||||
}}
|
||||
>
|
||||
<Icon name="cross" />
|
||||
</button>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
interface CancelSummaryProps {
|
||||
orderId?: string;
|
||||
marketId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple component for rendering a reasonable string from an order cancellation
|
||||
*/
|
||||
export const CancelSummary = ({ orderId, marketId }: CancelSummaryProps) => {
|
||||
return <span className="font-bold">{getLabel(orderId, marketId)}</span>;
|
||||
};
|
||||
|
||||
export function getLabel(
|
||||
orderId: string | undefined,
|
||||
marketId: string | undefined
|
||||
): string {
|
||||
if (!orderId && !marketId) {
|
||||
return t('All orders');
|
||||
} else if (marketId && !orderId) {
|
||||
return t('All in market');
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
@@ -5,11 +5,31 @@ import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getSearchType, SearchTypes, toHex } from './detect-search';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface FormFields {
|
||||
search: string;
|
||||
}
|
||||
|
||||
const MagnifyingGlass = () => (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1="12.8202"
|
||||
y1="13.1798"
|
||||
x2="17.0629"
|
||||
y2="17.4224"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
<circle cx="8" cy="8" r="7.5" stroke="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Search = () => {
|
||||
const { register, handleSubmit } = useForm<FormFields>();
|
||||
const navigate = useNavigate();
|
||||
@@ -49,39 +69,95 @@ export const Search = () => {
|
||||
[navigate]
|
||||
);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full md:max-w-[620px] justify-self-end"
|
||||
>
|
||||
<label htmlFor="search" className="sr-only">
|
||||
{t('Search by block number or transaction hash')}
|
||||
</label>
|
||||
<div className="flex items-stretch gap-2">
|
||||
<div className="flex grow relative">
|
||||
<Input
|
||||
{...register('search')}
|
||||
id="search"
|
||||
data-testid="search"
|
||||
className="text-white"
|
||||
hasError={Boolean(error?.message)}
|
||||
type="text"
|
||||
placeholder={t(
|
||||
'Enter block number, public key or transaction hash'
|
||||
)}
|
||||
/>
|
||||
{error?.message && (
|
||||
<div className="bg-white border border-t-0 border-accent absolute top-[100%] flex-1 w-full pb-2 px-2 rounded-b text-black">
|
||||
<InputError data-testid="search-error" intent="danger">
|
||||
{error.message}
|
||||
</InputError>
|
||||
</div>
|
||||
const searchForm = (
|
||||
<form className="block min-w-[290px]" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex relative items-stretch gap-2 text-xs">
|
||||
<label htmlFor="search" className="sr-only">
|
||||
{t('Search by block number or transaction hash')}
|
||||
</label>
|
||||
<button
|
||||
className={classNames(
|
||||
'absolute top-[50%] translate-y-[-50%] left-2',
|
||||
'text-vega-light-300 dark:text-vega-dark-300'
|
||||
)}
|
||||
</div>
|
||||
<Button type="submit" size="sm" data-testid="search-button">
|
||||
>
|
||||
<MagnifyingGlass />
|
||||
</button>
|
||||
<Input
|
||||
{...register('search')}
|
||||
id="search"
|
||||
data-testid="search"
|
||||
className={classNames(
|
||||
'peer',
|
||||
'pl-8 py-2 text-xs',
|
||||
'border rounded border-vega-light-200 dark:border-vega-dark-200'
|
||||
)}
|
||||
hasError={Boolean(error?.message)}
|
||||
type="text"
|
||||
placeholder={t('Enter block number, public key or transaction hash')}
|
||||
/>
|
||||
{error?.message && (
|
||||
<div
|
||||
className={classNames(
|
||||
'hidden peer-focus:block',
|
||||
'bg-white dark:bg-black',
|
||||
'border rounded-b border-t-0 border-vega-light-200 dark:border-vega-dark-200',
|
||||
'absolute top-[100%] flex-1 w-full pb-2 px-2 text-black dark:text-white'
|
||||
)}
|
||||
>
|
||||
<InputError
|
||||
data-testid="search-error"
|
||||
intent="danger"
|
||||
className="text-xs"
|
||||
>
|
||||
{error.message}
|
||||
</InputError>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
className="hidden [.search-dropdown_&]:block"
|
||||
type="submit"
|
||||
size="xs"
|
||||
data-testid="search-button"
|
||||
>
|
||||
{t('Search')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
const searchTrigger = (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button className="text-vega-light-300 dark:text-vega-dark-300 data-open:text-black dark:data-open:text-white flex items-center">
|
||||
<MagnifyingGlass />
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className={classNames(
|
||||
'search-dropdown',
|
||||
'p-2 min-w-[290px] z-20',
|
||||
'text-vega-light-300 dark:text-vega-dark-300',
|
||||
'bg-white dark:bg-black',
|
||||
'border rounded border-vega-light-200 dark:border-vega-dark-200',
|
||||
'shadow-[8px_8px_16px_0_rgba(0,0,0,0.4)]'
|
||||
)}
|
||||
align="end"
|
||||
sideOffset={10}
|
||||
>
|
||||
{searchForm}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="hidden [.nav-search-full_&]:block">{searchForm}</div>
|
||||
<div className="hidden [.nav-search-compact_&]:block">
|
||||
{searchTrigger}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { BatchCancellationInstruction } from '../../../../routes/types/bloc
|
||||
import { TxOrderType } from '../../tx-order-type';
|
||||
import { MarketLink } from '../../../links';
|
||||
import OrderSummary from '../../../order-summary/order-summary';
|
||||
import { CancelSummary } from '../../../order-summary/order-cancellation';
|
||||
|
||||
interface BatchCancelProps {
|
||||
index: number;
|
||||
@@ -19,7 +20,14 @@ export const BatchCancel = ({ index, submission }: BatchCancelProps) => {
|
||||
<TxOrderType orderType={'OrderCancellation'} />
|
||||
</td>
|
||||
<td>
|
||||
<OrderSummary id={submission.orderId} modifier="cancelled" />
|
||||
{submission.orderId ? (
|
||||
<OrderSummary id={submission.orderId} modifier="cancelled" />
|
||||
) : (
|
||||
<CancelSummary
|
||||
orderId={submission.orderId}
|
||||
marketId={submission.marketId}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<MarketLink id={submission.marketId} />
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
query ExplorerProposalStatus($id: ID!) {
|
||||
proposal(id: $id) {
|
||||
id
|
||||
state
|
||||
rejectionReason
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
query ExplorerNewAssetSignatureBundle($id: ID!) {
|
||||
erc20ListAssetBundle(assetId: $id) {
|
||||
signatures
|
||||
nonce
|
||||
}
|
||||
asset(id: $id) {
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
query ExplorerUpdateAssetSignatureBundle($id: ID!) {
|
||||
erc20SetAssetLimitsBundle(proposalId: $id) {
|
||||
signatures
|
||||
nonce
|
||||
}
|
||||
asset(id: $id) {
|
||||
status
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerProposalStatusQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerProposalStatusQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null } | null };
|
||||
|
||||
|
||||
export const ExplorerProposalStatusDocument = gql`
|
||||
query ExplorerProposalStatus($id: ID!) {
|
||||
proposal(id: $id) {
|
||||
id
|
||||
state
|
||||
rejectionReason
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerProposalStatusQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerProposalStatusQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerProposalStatusQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerProposalStatusQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerProposalStatusQuery(baseOptions: Apollo.QueryHookOptions<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>(ExplorerProposalStatusDocument, options);
|
||||
}
|
||||
export function useExplorerProposalStatusLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>(ExplorerProposalStatusDocument, options);
|
||||
}
|
||||
export type ExplorerProposalStatusQueryHookResult = ReturnType<typeof useExplorerProposalStatusQuery>;
|
||||
export type ExplorerProposalStatusLazyQueryHookResult = ReturnType<typeof useExplorerProposalStatusLazyQuery>;
|
||||
export type ExplorerProposalStatusQueryResult = Apollo.QueryResult<ExplorerProposalStatusQuery, ExplorerProposalStatusQueryVariables>;
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
import * as Types from '@vegaprotocol/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type ExplorerNewAssetSignatureBundleQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerNewAssetSignatureBundleQuery = { __typename?: 'Query', erc20ListAssetBundle?: { __typename?: 'Erc20ListAssetBundle', signatures: string, nonce: string } | null, asset?: { __typename?: 'Asset', status: Types.AssetStatus } | null };
|
||||
|
||||
export type ExplorerUpdateAssetSignatureBundleQueryVariables = Types.Exact<{
|
||||
id: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ExplorerUpdateAssetSignatureBundleQuery = { __typename?: 'Query', erc20SetAssetLimitsBundle: { __typename?: 'ERC20SetAssetLimitsBundle', signatures: string, nonce: string }, asset?: { __typename?: 'Asset', status: Types.AssetStatus } | null };
|
||||
|
||||
|
||||
export const ExplorerNewAssetSignatureBundleDocument = gql`
|
||||
query ExplorerNewAssetSignatureBundle($id: ID!) {
|
||||
erc20ListAssetBundle(assetId: $id) {
|
||||
signatures
|
||||
nonce
|
||||
}
|
||||
asset(id: $id) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerNewAssetSignatureBundleQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerNewAssetSignatureBundleQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerNewAssetSignatureBundleQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerNewAssetSignatureBundleQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerNewAssetSignatureBundleQuery(baseOptions: Apollo.QueryHookOptions<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>(ExplorerNewAssetSignatureBundleDocument, options);
|
||||
}
|
||||
export function useExplorerNewAssetSignatureBundleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>(ExplorerNewAssetSignatureBundleDocument, options);
|
||||
}
|
||||
export type ExplorerNewAssetSignatureBundleQueryHookResult = ReturnType<typeof useExplorerNewAssetSignatureBundleQuery>;
|
||||
export type ExplorerNewAssetSignatureBundleLazyQueryHookResult = ReturnType<typeof useExplorerNewAssetSignatureBundleLazyQuery>;
|
||||
export type ExplorerNewAssetSignatureBundleQueryResult = Apollo.QueryResult<ExplorerNewAssetSignatureBundleQuery, ExplorerNewAssetSignatureBundleQueryVariables>;
|
||||
export const ExplorerUpdateAssetSignatureBundleDocument = gql`
|
||||
query ExplorerUpdateAssetSignatureBundle($id: ID!) {
|
||||
erc20SetAssetLimitsBundle(proposalId: $id) {
|
||||
signatures
|
||||
nonce
|
||||
}
|
||||
asset(id: $id) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useExplorerUpdateAssetSignatureBundleQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useExplorerUpdateAssetSignatureBundleQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useExplorerUpdateAssetSignatureBundleQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useExplorerUpdateAssetSignatureBundleQuery({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useExplorerUpdateAssetSignatureBundleQuery(baseOptions: Apollo.QueryHookOptions<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>(ExplorerUpdateAssetSignatureBundleDocument, options);
|
||||
}
|
||||
export function useExplorerUpdateAssetSignatureBundleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>(ExplorerUpdateAssetSignatureBundleDocument, options);
|
||||
}
|
||||
export type ExplorerUpdateAssetSignatureBundleQueryHookResult = ReturnType<typeof useExplorerUpdateAssetSignatureBundleQuery>;
|
||||
export type ExplorerUpdateAssetSignatureBundleLazyQueryHookResult = ReturnType<typeof useExplorerUpdateAssetSignatureBundleLazyQuery>;
|
||||
export type ExplorerUpdateAssetSignatureBundleQueryResult = Apollo.QueryResult<ExplorerUpdateAssetSignatureBundleQuery, ExplorerUpdateAssetSignatureBundleQueryVariables>;
|
||||
@@ -0,0 +1,70 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
|
||||
import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
|
||||
|
||||
type Terms = components['schemas']['vegaProposalTerms'];
|
||||
|
||||
export function format(date: string | undefined, def: string) {
|
||||
if (!date) {
|
||||
return def;
|
||||
}
|
||||
|
||||
return new Date().toLocaleDateString() || def;
|
||||
}
|
||||
|
||||
export function getDate(
|
||||
data: ExplorerProposalStatusQuery | undefined,
|
||||
terms: Terms
|
||||
): string {
|
||||
const DEFAULT = t('Unknown');
|
||||
if (!data?.proposal?.state) {
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
switch (data.proposal.state) {
|
||||
case 'STATE_DECLINED':
|
||||
return `${t('Rejected on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
|
||||
case 'STATE_ENACTED':
|
||||
return `${t('Vote passed on')}: ${format(
|
||||
terms.enactmentTimestamp,
|
||||
DEFAULT
|
||||
)}`;
|
||||
case 'STATE_FAILED':
|
||||
return `${t('Failed on')}: ${format(terms.validationTimestamp, DEFAULT)}`;
|
||||
case 'STATE_OPEN':
|
||||
return `${t('Open until')}: ${format(terms.closingTimestamp, DEFAULT)}`;
|
||||
case 'STATE_PASSED':
|
||||
return `${t('Passed on')}: ${format(terms.closingTimestamp, DEFAULT)}`;
|
||||
case 'STATE_REJECTED':
|
||||
return `${t('Rejected on submission')}`;
|
||||
case 'STATE_WAITING_FOR_NODE_VOTE':
|
||||
return `${t('Opening')}...`;
|
||||
default:
|
||||
return DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
interface ProposalDateProps {
|
||||
id: string;
|
||||
terms: Terms;
|
||||
}
|
||||
/**
|
||||
* Shows the most relevant date for the proposal summary view. Depending on the
|
||||
* state returned by GraphQL, we show either the validation, closing or enactment
|
||||
* timestamp
|
||||
*/
|
||||
export const ProposalDate = ({ terms, id }: ProposalDateProps) => {
|
||||
const { data } = useExplorerProposalStatusQuery({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Lozenge className="font-sans text-xs float-right">
|
||||
{getDate(data, terms)}
|
||||
</Lozenge>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconProps } from '@vegaprotocol/ui-toolkit';
|
||||
import { useExplorerProposalStatusQuery } from './__generated__/Proposal';
|
||||
import type { ExplorerProposalStatusQuery } from './__generated__/Proposal';
|
||||
import type * as Apollo from '@apollo/client';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
type ProposalQueryResult = Apollo.QueryResult<
|
||||
ExplorerProposalStatusQuery,
|
||||
Types.Exact<{
|
||||
id: string;
|
||||
}>
|
||||
>;
|
||||
|
||||
interface ProposalStatusIconProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
type IconAndLabel = {
|
||||
icon: IconProps['name'];
|
||||
label: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Select an icon for a given query result. Tolerates queries that don't return
|
||||
* any data
|
||||
*
|
||||
* @param data a data result from useExplorerProposalStatusQuery
|
||||
* @returns Icon name
|
||||
*/
|
||||
export function getIconAndLabelForStatus(
|
||||
res: ProposalQueryResult
|
||||
): IconAndLabel {
|
||||
const DEFAULT: IconAndLabel = {
|
||||
icon: 'error',
|
||||
label: t('Proposal state unknown'),
|
||||
};
|
||||
|
||||
if (res.loading) {
|
||||
return {
|
||||
icon: 'more',
|
||||
label: t('Loading data'),
|
||||
};
|
||||
}
|
||||
|
||||
if (!res?.data?.proposal || res.error) {
|
||||
return {
|
||||
icon: 'error',
|
||||
label: res.error?.message || DEFAULT.label,
|
||||
};
|
||||
}
|
||||
|
||||
switch (res.data.proposal.state) {
|
||||
case 'STATE_DECLINED':
|
||||
return {
|
||||
icon: 'stop',
|
||||
label: t('Proposal did not have enough participation to be valid'),
|
||||
};
|
||||
case 'STATE_ENACTED':
|
||||
return {
|
||||
icon: 'tick-circle',
|
||||
label: t('Vote passed and the proposal has been enacted'),
|
||||
};
|
||||
case 'STATE_FAILED':
|
||||
return {
|
||||
icon: 'thumbs-down',
|
||||
label: t('Proposal became invalid and was not processed'),
|
||||
};
|
||||
case 'STATE_OPEN':
|
||||
return {
|
||||
// A checklist to indicate in progress
|
||||
icon: 'form',
|
||||
label: t('Voting is in progress'),
|
||||
};
|
||||
case 'STATE_PASSED':
|
||||
return {
|
||||
icon: 'thumbs-up',
|
||||
label: t(
|
||||
'Voting is complete and this proposal was approved. It is not yet enacted.'
|
||||
),
|
||||
};
|
||||
case 'STATE_REJECTED':
|
||||
return {
|
||||
icon: 'disable',
|
||||
label: t('The proposal was invalid'),
|
||||
};
|
||||
case 'STATE_WAITING_FOR_NODE_VOTE':
|
||||
return {
|
||||
// A sparkly thing indicating it's new
|
||||
icon: 'clean',
|
||||
label: t('Proposal is being checked by validators'),
|
||||
};
|
||||
default:
|
||||
return DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export const ProposalStatusIcon = ({ id }: ProposalStatusIconProps) => {
|
||||
const { icon, label } = getIconAndLabelForStatus(
|
||||
useExplorerProposalStatusQuery({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="float-left mr-3">
|
||||
<Tooltip description={<p>{label}</p>}>
|
||||
<Icon name={icon} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import { BundleError } from './signature-bundle/bundle-error';
|
||||
import { BundleExists } from './signature-bundle/bundle-exists';
|
||||
import { useExplorerNewAssetSignatureBundleQuery } from './__generated__/SignatureBundle';
|
||||
|
||||
export interface ProposalSignatureBundleByTypeProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a proposal needs a signature bundle, AND that signature bundle exists
|
||||
* AND that proposal is a New Asset Proposal, render an overview of the
|
||||
* signature bundle. Or an error.
|
||||
*
|
||||
* There is an almost identical component, ProposalSignatureBundleUpdateAsset
|
||||
*/
|
||||
export const ProposalSignatureBundleNewAsset = ({
|
||||
id,
|
||||
}: ProposalSignatureBundleByTypeProps) => {
|
||||
const { data, error, loading } = useExplorerNewAssetSignatureBundleQuery({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (data?.erc20ListAssetBundle?.signatures) {
|
||||
return (
|
||||
<BundleExists
|
||||
signatures={data.erc20ListAssetBundle.signatures}
|
||||
nonce={data.erc20ListAssetBundle.nonce}
|
||||
status={data.asset?.status}
|
||||
proposalId={id}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <BundleError status={data?.asset?.status} error={error} />;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ProposalSignatureBundleByTypeProps } from './signature-bundle-new';
|
||||
import { BundleError } from './signature-bundle/bundle-error';
|
||||
import { BundleExists } from './signature-bundle/bundle-exists';
|
||||
import { useExplorerUpdateAssetSignatureBundleQuery } from './__generated__/SignatureBundle';
|
||||
|
||||
/**
|
||||
* If a proposal needs a signature bundle, AND that signature bundle exists
|
||||
* AND that proposal is a Asset Limits Proposal, render an overview of the
|
||||
* signature bundle. Or an error.
|
||||
*
|
||||
* There is an almost identical component, ProposalSignatureBundleNewAsset
|
||||
*/
|
||||
export const ProposalSignatureBundleUpdateAsset = ({
|
||||
id,
|
||||
}: ProposalSignatureBundleByTypeProps) => {
|
||||
const { data, error, loading } = useExplorerUpdateAssetSignatureBundleQuery({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (data?.erc20SetAssetLimitsBundle?.signatures) {
|
||||
return (
|
||||
<BundleExists
|
||||
signatures={data.erc20SetAssetLimitsBundle.signatures}
|
||||
nonce={data.erc20SetAssetLimitsBundle.nonce}
|
||||
status={data.asset?.status}
|
||||
proposalId={id}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <BundleError status={data?.asset?.status} error={error} />;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ProposalSignatureBundleNewAsset } from './signature-bundle-new';
|
||||
import { ProposalSignatureBundleUpdateAsset } from './signature-bundle-update';
|
||||
|
||||
export function format(date: string | undefined, def: string) {
|
||||
if (!date) {
|
||||
return def;
|
||||
}
|
||||
|
||||
return new Date().toLocaleDateString() || def;
|
||||
}
|
||||
|
||||
interface ProposalSignatureBundleProps {
|
||||
id: string;
|
||||
type: 'NewAsset' | 'UpdateAsset';
|
||||
}
|
||||
|
||||
/**
|
||||
* Some proposals, if enacted, generate a signature bundle.
|
||||
* The queries have to be split due to the way the API returns
|
||||
* errors, hence this slightly redundant feeling switcher.
|
||||
*/
|
||||
export const ProposalSignatureBundle = ({
|
||||
id,
|
||||
type,
|
||||
}: ProposalSignatureBundleProps) => {
|
||||
return type === 'NewAsset' ? (
|
||||
<ProposalSignatureBundleNewAsset id={id} />
|
||||
) : (
|
||||
<ProposalSignatureBundleUpdateAsset id={id} />
|
||||
);
|
||||
};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import { AssetStatus } from '@vegaprotocol/types';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { BundleError } from './bundle-error';
|
||||
|
||||
describe('Bundle Error', () => {
|
||||
const NON_ENABLED_STATUS: AssetStatus[] = [
|
||||
AssetStatus.STATUS_PENDING_LISTING,
|
||||
AssetStatus.STATUS_PROPOSED,
|
||||
AssetStatus.STATUS_REJECTED,
|
||||
];
|
||||
|
||||
const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
|
||||
|
||||
it.each(NON_ENABLED_STATUS)(
|
||||
'shows the apollo error if not enabled and a message is provided',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleError
|
||||
error={{ message: 'test-error-message' } as ApolloError}
|
||||
status={status}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByText('test-error-message')).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
it.each(NON_ENABLED_STATUS)(
|
||||
'shows some fallback error message if the bundle has not been used and there is no error',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleError status={status} />
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByText('No bundle for proposal ID')).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
it.each(ENABLED_STATUS)(
|
||||
'hides ProposalLink if the status is enabled',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleError
|
||||
error={{ message: 'irrelevant ' } as ApolloError}
|
||||
status={status}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Asset already enabled')).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
});
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import type { AssetStatus } from '@vegaprotocol/types';
|
||||
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import Hash from '../../../../links/hash';
|
||||
import { IconForBundleStatus } from './bundle-icon';
|
||||
|
||||
export interface BundleErrorProps {
|
||||
status?: AssetStatus;
|
||||
error?: ApolloError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders if a proposal signature bundle cannot be found.
|
||||
* It is also possible that a data node has dropped the bundle
|
||||
* from its retention so there is a backup case where we check
|
||||
* the status - if it's already enabled, pretend this isn't an error
|
||||
*/
|
||||
export const BundleError = ({ status, error }: BundleErrorProps) => {
|
||||
return (
|
||||
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
|
||||
<IconForBundleStatus status={status} />
|
||||
<h1 className="text-xl pb-1">{t('No signature bundle found')}</h1>
|
||||
|
||||
<p>
|
||||
{status === 'STATUS_ENABLED' ? (
|
||||
t('Asset already enabled')
|
||||
) : (
|
||||
<Hash text={error ? error.message : t('No bundle for proposal ID')} />
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { render } from '@testing-library/react';
|
||||
import { AssetStatus } from '@vegaprotocol/types';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { BundleExists } from './bundle-exists';
|
||||
|
||||
describe('Bundle Exists', () => {
|
||||
const NON_ENABLED_STATUS: AssetStatus[] = [
|
||||
AssetStatus.STATUS_PENDING_LISTING,
|
||||
AssetStatus.STATUS_PROPOSED,
|
||||
AssetStatus.STATUS_REJECTED,
|
||||
];
|
||||
|
||||
const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
|
||||
|
||||
const MOCK_SIGNATURES =
|
||||
'0x1760ce4efec01d5bb9fe57c0660a39a27e0b5a1bd39b4d3fc0452bf142a4ef733baaf4dbedd74cd676c759f96ac7f412ec05e136bbff8a81d9f0c2428df8e099004c4d166ece0527d86e202386e8758580790d0a46f6429baaa268b4bf5c11c9e3402e175a9022ae8295e543853c01383ef17cd58458ddfc9c706fca0ecffa0d3d0160eb5234e63a78b9649428ca7659eb693fdde86edfc6c2707ef2e8fbf4c76a9f0eebe183da571a58837e2fa995d7b10955ecf04c138dc0ce964f17c3de1f5c6d0060ec132cc515cf974ead1da38e2ff8d4b870335865e8d4d8c982182bddea18bb513e2d37fd32de7fedc0c4f694e6bcdcf20f8547f19e7d9d25ce32c6ca9ea51e01ad3b92475778d9da7251d3943071f59107c9cd7ad9dd1923c06e88d3352869d919a591bf30732bad2c3fcf30a9f664dbb7a9c65a64875a48cbeb5741bd0a853901';
|
||||
const MOCK_NONCE =
|
||||
'18250011763873610289536200551900545467959221115607409799241178172533618346952';
|
||||
const MOCK_PROPOSAL_ID =
|
||||
'285923fed8c66ffb416b163e8ec72d3a87b9b8e2570e7ee7fe97d7092a918bc8';
|
||||
|
||||
const PROPOSAL_LINK_TEXT = 'Visit our Governance site to submit this';
|
||||
|
||||
it.each(NON_ENABLED_STATUS)(
|
||||
'shows a handy ProposalLink if the status is anything except enabled',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleExists
|
||||
nonce={MOCK_NONCE}
|
||||
proposalId={MOCK_PROPOSAL_ID}
|
||||
signatures={MOCK_SIGNATURES}
|
||||
status={status}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByText(PROPOSAL_LINK_TEXT)).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
it.each(ENABLED_STATUS)(
|
||||
'hides ProposalLink if the status is enabled',
|
||||
(status) => {
|
||||
const screen = render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<BundleExists
|
||||
nonce={MOCK_NONCE}
|
||||
proposalId={MOCK_PROPOSAL_ID}
|
||||
signatures={MOCK_SIGNATURES}
|
||||
status={status}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(screen.queryAllByText(PROPOSAL_LINK_TEXT)).toEqual([]);
|
||||
}
|
||||
);
|
||||
});
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { AssetStatus } from '@vegaprotocol/types';
|
||||
import ProposalLink from '../../../../links/proposal-link/proposal-link';
|
||||
import { IconForBundleStatus } from './bundle-icon';
|
||||
import { ProposalSignatureBundleDetails } from './details';
|
||||
|
||||
export interface BundleExistsProps {
|
||||
signatures: string;
|
||||
nonce: string;
|
||||
status?: AssetStatus;
|
||||
proposalId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a proposal needs a signature bundle, AND that signature bundle exists,
|
||||
* this component renders that signature bundle.
|
||||
*
|
||||
*/
|
||||
export const BundleExists = ({
|
||||
signatures,
|
||||
nonce,
|
||||
status,
|
||||
proposalId,
|
||||
}: BundleExistsProps) => {
|
||||
return (
|
||||
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5 mt-5">
|
||||
<IconForBundleStatus status={status} />
|
||||
<h1 className="text-xl pb-1">
|
||||
{status === 'STATUS_ENABLED'
|
||||
? t('Asset added to bridge')
|
||||
: t('Signature bundle generated')}
|
||||
</h1>
|
||||
|
||||
<ProposalSignatureBundleDetails signatures={signatures} nonce={nonce} />
|
||||
|
||||
{status !== 'STATUS_ENABLED' ? (
|
||||
<p className="mt-5">
|
||||
<ProposalLink
|
||||
id={proposalId}
|
||||
text={t('Visit our Governance site to submit this')}
|
||||
/>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { AssetStatus } from '@vegaprotocol/types';
|
||||
import { IconForBundleStatus } from './bundle-icon';
|
||||
|
||||
describe('Bundle status icon', () => {
|
||||
const NON_ENABLED_STATUS: AssetStatus[] = [
|
||||
AssetStatus.STATUS_PENDING_LISTING,
|
||||
AssetStatus.STATUS_PROPOSED,
|
||||
AssetStatus.STATUS_REJECTED,
|
||||
];
|
||||
|
||||
const ENABLED_STATUS: AssetStatus[] = [AssetStatus.STATUS_ENABLED];
|
||||
|
||||
it.each(NON_ENABLED_STATUS)(
|
||||
'show a sparkle icon if the bundle is unused',
|
||||
(status) => {
|
||||
const screen = render(<IconForBundleStatus status={status} />);
|
||||
const i = screen.getByRole('img');
|
||||
expect(i).toHaveAttribute('aria-label');
|
||||
expect(i.getAttribute('aria-label')).toMatch(/clean/);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(ENABLED_STATUS)(
|
||||
'shows a tick if the bundle is already used',
|
||||
(status) => {
|
||||
const screen = render(<IconForBundleStatus status={status} />);
|
||||
const i = screen.getByRole('img');
|
||||
expect(i).toHaveAttribute('aria-label');
|
||||
expect(i.getAttribute('aria-label')).toMatch(/tick-circle/);
|
||||
}
|
||||
);
|
||||
});
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import type { AssetStatus } from '@vegaprotocol/types';
|
||||
import type { IconName } from '@vegaprotocol/ui-toolkit';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export interface IconForBundleStatusProps {
|
||||
status?: AssetStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Naively select an icon for an asset. If it is enabled, we show a tick - anything
|
||||
* else is assumed to be 'in progress'. There should only be a signature bundle or the
|
||||
* asset should not exist
|
||||
*/
|
||||
export const IconForBundleStatus = ({ status }: IconForBundleStatusProps) => {
|
||||
const i: IconName = status === 'STATUS_ENABLED' ? 'tick-circle' : 'clean';
|
||||
return <Icon className="float-left mt-2 mr-3" name={i} />;
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export interface ProposalSignatureBundleDetailsProps {
|
||||
signatures: string;
|
||||
nonce: string;
|
||||
}
|
||||
|
||||
export const ProposalSignatureBundleDetails = ({
|
||||
signatures,
|
||||
nonce,
|
||||
}: ProposalSignatureBundleDetailsProps) => {
|
||||
return (
|
||||
<details className="mt-5">
|
||||
<summary>{t('Signature bundle details')}</summary>
|
||||
|
||||
<div className="ml-4">
|
||||
<h2 className="text-lg mt-2 mb-2">{t('Signatures')}</h2>
|
||||
<p>
|
||||
<textarea
|
||||
className="font-mono bg-neutral-300 text-[11px] leading-3 text-gray-900 w-full p-2 max-w-[615px]"
|
||||
readOnly={true}
|
||||
rows={12}
|
||||
cols={120}
|
||||
value={signatures}
|
||||
/>
|
||||
</p>
|
||||
|
||||
<h2 className="text-lg mt-5 mb-2">{t('Nonce')}</h2>
|
||||
<p>
|
||||
<textarea
|
||||
className="font-mono bg-neutral-300 text-[11px] leading-3 text-gray-900 w-full p-2 max-w-[615px]"
|
||||
readOnly={true}
|
||||
rows={2}
|
||||
cols={120}
|
||||
value={nonce}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import type { ProposalTerms } from '../tx-proposal';
|
||||
import { useState } from 'react';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { JsonViewerDialog } from '../../../dialogs/json-viewer-dialog';
|
||||
import ProposalLink from '../../../links/proposal-link/proposal-link';
|
||||
import truncate from 'lodash/truncate';
|
||||
import { ProposalStatusIcon } from './proposal-status-icon';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { ProposalDate } from './proposal-date';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
type Rationale = components['schemas']['vegaProposalRationale'];
|
||||
|
||||
type ProposalTermsDialog = {
|
||||
open: boolean;
|
||||
title: string;
|
||||
content: unknown;
|
||||
};
|
||||
|
||||
interface ProposalSummaryProps {
|
||||
id: string;
|
||||
rationale?: Rationale;
|
||||
terms?: ProposalTerms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Effectively a 'preview' for what the proposal is about, and a link to the
|
||||
* Token site for full breakdown of votes
|
||||
*/
|
||||
export const ProposalSummary = ({
|
||||
id,
|
||||
rationale,
|
||||
terms,
|
||||
}: ProposalSummaryProps) => {
|
||||
const [dialog, setDialog] = useState<ProposalTermsDialog>({
|
||||
open: false,
|
||||
title: '',
|
||||
content: null,
|
||||
});
|
||||
|
||||
const openDialog = () => {
|
||||
if (!terms) return;
|
||||
|
||||
setDialog({
|
||||
open: true,
|
||||
title: rationale?.title || t('Proposal details'),
|
||||
content: terms ? terms : {},
|
||||
});
|
||||
};
|
||||
|
||||
const md =
|
||||
rationale && rationale.description
|
||||
? truncate(rationale.description, {
|
||||
// Limits the description to roughly 5 lines, maximum
|
||||
length: 350,
|
||||
})
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div className="w-auto max-w-lg border-2 border-solid border-vega-light-100 dark:border-vega-dark-200 p-5">
|
||||
{id && <ProposalStatusIcon id={id} />}
|
||||
{rationale?.title && <h1 className="text-xl pb-1">{rationale.title}</h1>}
|
||||
{rationale?.description && (
|
||||
<p className="pt-2 text-sm leading-tight">
|
||||
<ReactMarkdown
|
||||
className="react-markdown-container"
|
||||
skipHtml={true}
|
||||
disallowedElements={['img']}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{md}
|
||||
</ReactMarkdown>
|
||||
</p>
|
||||
)}
|
||||
<p className="pt-5">
|
||||
<button className="underline max-md:hidden mr-5" onClick={openDialog}>
|
||||
{t('View terms')}
|
||||
</button>{' '}
|
||||
<ProposalLink id={id} text={t('Full details')} />
|
||||
{terms && <ProposalDate terms={terms} id={id} />}
|
||||
</p>
|
||||
<JsonViewerDialog
|
||||
open={dialog.open}
|
||||
onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })}
|
||||
title={dialog.title}
|
||||
content={dialog.content}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -55,7 +55,7 @@ export const TxDetailsShared = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
|
||||
<TableCell>
|
||||
<Hash text={txData.hash} />
|
||||
<Hash text={txData.hash.toLowerCase()} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
|
||||
@@ -24,6 +24,7 @@ import { TxDetailsProtocolUpgrade } from './tx-details-protocol-upgrade';
|
||||
import { TxDetailsIssueSignatures } from './tx-issue-signatures';
|
||||
import { TxDetailsNodeAnnounce } from './tx-node-announce';
|
||||
import { TxDetailsStateVariable } from './tx-state-variable-proposal';
|
||||
import { TxProposal } from './tx-proposal';
|
||||
import { TxDetailsTransfer } from './tx-transfer';
|
||||
|
||||
interface TxDetailsWrapperProps {
|
||||
@@ -88,6 +89,8 @@ function getTransactionComponent(txData?: BlockExplorerTransactionResult) {
|
||||
return TxDetailsOrderAmend;
|
||||
case 'Validator Heartbeat':
|
||||
return TxDetailsHeartbeat;
|
||||
case 'Proposal':
|
||||
return TxProposal;
|
||||
case 'Vote on Proposal':
|
||||
return TxProposalVote;
|
||||
case 'Batch Market Instructions':
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import DeterministicOrderDetails from '../../order-details/deterministic-order-details';
|
||||
import { CancelSummary } from '../../order-summary/order-cancellation';
|
||||
import Hash from '../../links/hash';
|
||||
|
||||
interface TxDetailsOrderCancelProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -24,8 +26,8 @@ export const TxDetailsOrderCancel = ({
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const marketId: string = txData.command.orderCancellation.marketId || '-';
|
||||
const orderId: string = txData.command.orderCancellation.orderId || '-';
|
||||
const marketId: string = txData.command.orderCancellation.marketId;
|
||||
const orderId: string = txData.command.orderCancellation.orderId;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -38,18 +40,24 @@ export const TxDetailsOrderCancel = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Order')}</TableCell>
|
||||
<TableCell>
|
||||
<code>{orderId}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
{orderId ? (
|
||||
<Hash text={orderId} />
|
||||
) : (
|
||||
<CancelSummary orderId={orderId} marketId={marketId} />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{marketId ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
|
||||
{orderId !== '-' ? <DeterministicOrderDetails id={orderId} /> : null}
|
||||
{orderId ? <DeterministicOrderDetails id={orderId} /> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
|
||||
import { sharedHeaderProps, TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import type { components } from '../../../../types/explorer';
|
||||
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
import has from 'lodash/has';
|
||||
import { ProposalSummary } from './proposal/summary';
|
||||
import Hash from '../../links/hash';
|
||||
import { ProposalSignatureBundle } from './proposal/signature-bundle';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export type Proposal = components['schemas']['v1ProposalSubmission'];
|
||||
export type ProposalTerms = components['schemas']['vegaProposalTerms'];
|
||||
|
||||
interface TxProposalProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
pubKey: string | undefined;
|
||||
blockData: TendermintBlocksResponse | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if a given proposal requires a signature bundle to be emitted by the validators for
|
||||
* the proposal to be completed after it is accepted by the governance vote
|
||||
*
|
||||
* @param t The details of the proposal
|
||||
* @returns boolean True if a signature bundle is required. Used to fetch a signature bundle
|
||||
*/
|
||||
export function proposalRequiresSignatureBundle(proposal?: Proposal): boolean {
|
||||
if (!proposal?.terms) {
|
||||
return false;
|
||||
}
|
||||
return !!['newAsset', 'updateAsset'].filter((requiredIfExists) =>
|
||||
has(proposal.terms, requiredIfExists)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a proposal, returns a nice text label for the proposal type
|
||||
*
|
||||
* @param terms The details of the proposal
|
||||
* @returns string Proposal type
|
||||
*/
|
||||
export function proposalTypeLabel(terms?: ProposalTerms): string {
|
||||
if (has(terms, 'newAsset')) {
|
||||
return t('New asset proposal');
|
||||
} else if (has(terms, 'updateAsset')) {
|
||||
return t('Update asset proposal');
|
||||
} else if (has(terms, 'newMarket')) {
|
||||
return t('New market proposal');
|
||||
} else if (has(terms, 'updateMarket')) {
|
||||
return t('Update market proposal');
|
||||
} else if (has(terms, 'updateNetworkParameter')) {
|
||||
return t('Update network parameter');
|
||||
} else if (has(terms, 'newFreeform')) {
|
||||
return t('Freeform proposal');
|
||||
}
|
||||
|
||||
// The list above contains all currently known types. This will be triggered if a new
|
||||
// unrecognised proposal type is added.
|
||||
return t('Governance proposal');
|
||||
}
|
||||
|
||||
/**
|
||||
* A proposal. It's more simplistic than man other views because there are already other, better
|
||||
* ways to view things about a proposal!
|
||||
*
|
||||
*/
|
||||
export const TxProposal = ({ txData, pubKey, blockData }: TxProposalProps) => {
|
||||
if (!txData || !txData.command.proposalSubmission) {
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
let deterministicId = '';
|
||||
|
||||
const proposal: Proposal = txData.command.proposalSubmission;
|
||||
const sig = txData?.signature?.value;
|
||||
if (sig) {
|
||||
deterministicId = txSignatureToDeterministicId(sig);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||
<TableCell>{proposalTypeLabel(proposal.terms)}</TableCell>
|
||||
</TableRow>
|
||||
{/* TODO: Disable type row */}
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
blockData={blockData}
|
||||
hideTypeRow={true}
|
||||
/>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Proposal ID')}</TableCell>
|
||||
<TableCell>
|
||||
<Hash text={deterministicId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableWithTbody>
|
||||
<ProposalSummary
|
||||
id={deterministicId}
|
||||
rationale={proposal.rationale}
|
||||
terms={proposal?.terms}
|
||||
/>
|
||||
{proposalRequiresSignatureBundle(proposal) && (
|
||||
<ProposalSignatureBundle
|
||||
id={deterministicId}
|
||||
type={proposal.terms?.newAsset ? 'NewAsset' : 'UpdateAsset'}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -12,6 +12,7 @@ export const Proposals = () => {
|
||||
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: proposalsDataProvider,
|
||||
variables: {},
|
||||
});
|
||||
|
||||
useDocumentTitle([t('Governance Proposals')]);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { MarketDetails } from '../../components/markets/market-details';
|
||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import compact from 'lodash/compact';
|
||||
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
|
||||
import { marketInfoNoCandlesDataProvider } from '@vegaprotocol/market-info';
|
||||
import { marketInfoProvider } from '@vegaprotocol/market-info';
|
||||
import { PageTitle } from '../../components/page-helpers/page-title';
|
||||
|
||||
export const MarketPage = () => {
|
||||
@@ -16,24 +16,17 @@ export const MarketPage = () => {
|
||||
|
||||
const { marketId } = useParams<{ marketId: string }>();
|
||||
|
||||
const variables = useMemo(
|
||||
() => ({
|
||||
marketId,
|
||||
}),
|
||||
[marketId]
|
||||
);
|
||||
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: marketInfoNoCandlesDataProvider,
|
||||
dataProvider: marketInfoProvider,
|
||||
skipUpdates: true,
|
||||
variables,
|
||||
variables: {
|
||||
marketId: marketId || '',
|
||||
skip: !marketId,
|
||||
},
|
||||
});
|
||||
|
||||
useDocumentTitle(
|
||||
compact([
|
||||
'Market details',
|
||||
data?.market?.tradableInstrument.instrument.name,
|
||||
])
|
||||
compact(['Market details', data?.tradableInstrument.instrument.name])
|
||||
);
|
||||
|
||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||
@@ -43,10 +36,10 @@ export const MarketPage = () => {
|
||||
<section className="relative">
|
||||
<PageTitle
|
||||
data-testid="markets-heading"
|
||||
title={data?.market?.tradableInstrument.instrument.name || ''}
|
||||
title={data?.tradableInstrument.instrument.name || ''}
|
||||
actions={
|
||||
<Button
|
||||
disabled={!data?.market}
|
||||
disabled={!data}
|
||||
size="xs"
|
||||
onClick={() => setDialogOpen(true)}
|
||||
>
|
||||
@@ -60,14 +53,14 @@ export const MarketPage = () => {
|
||||
loading={loading}
|
||||
error={error}
|
||||
>
|
||||
<MarketDetails market={data?.market} />
|
||||
{data && <MarketDetails market={data} />}
|
||||
</AsyncRenderer>
|
||||
</section>
|
||||
<JsonViewerDialog
|
||||
open={dialogOpen}
|
||||
onChange={(isOpen) => setDialogOpen(isOpen)}
|
||||
title={data?.market?.tradableInstrument.instrument.name || ''}
|
||||
content={data?.market}
|
||||
title={data?.tradableInstrument.instrument.name || ''}
|
||||
content={data}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ export const MarketsPage = () => {
|
||||
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: marketsProvider,
|
||||
variables: undefined,
|
||||
skipUpdates: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -23,14 +23,18 @@ import { NetworkParameters } from './network-parameters';
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import { MarketPage, MarketsPage } from './markets';
|
||||
|
||||
export type Navigable = { path: string; name: string; text: string };
|
||||
export type Navigable = {
|
||||
path: string;
|
||||
name: string;
|
||||
text: string;
|
||||
};
|
||||
type Route = RouteObject & Navigable;
|
||||
|
||||
const partiesRoutes: Route[] = flags.parties
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
name: 'Parties',
|
||||
name: t('Parties'),
|
||||
text: t('Parties'),
|
||||
element: <Party />,
|
||||
children: [
|
||||
@@ -52,7 +56,7 @@ const assetsRoutes: Route[] = flags.assets
|
||||
{
|
||||
path: Routes.ASSETS,
|
||||
text: t('Assets'),
|
||||
name: 'Assets',
|
||||
name: t('Assets'),
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@@ -71,7 +75,7 @@ const genesisRoutes: Route[] = flags.genesis
|
||||
? [
|
||||
{
|
||||
path: Routes.GENESIS,
|
||||
name: 'Genesis',
|
||||
name: t('Genesis'),
|
||||
text: t('Genesis Parameters'),
|
||||
element: <Genesis />,
|
||||
},
|
||||
@@ -82,7 +86,7 @@ const governanceRoutes: Route[] = flags.governance
|
||||
? [
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
name: 'Governance proposals',
|
||||
name: t('Governance proposals'),
|
||||
text: t('Governance Proposals'),
|
||||
element: <Proposals />,
|
||||
},
|
||||
@@ -93,7 +97,7 @@ const marketsRoutes: Route[] = flags.markets
|
||||
? [
|
||||
{
|
||||
path: Routes.MARKETS,
|
||||
name: 'Markets',
|
||||
name: t('Markets'),
|
||||
text: t('Markets'),
|
||||
children: [
|
||||
{
|
||||
@@ -113,17 +117,18 @@ const networkParametersRoutes: Route[] = flags.networkParameters
|
||||
? [
|
||||
{
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
name: 'NetworkParameters',
|
||||
name: t('NetworkParameters'),
|
||||
text: t('Network Parameters'),
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const validators: Route[] = flags.validators
|
||||
? [
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
name: 'Validators',
|
||||
name: t('Validators'),
|
||||
text: t('Validators'),
|
||||
element: <ValidatorsPage />,
|
||||
},
|
||||
@@ -133,14 +138,14 @@ const validators: Route[] = flags.validators
|
||||
const routerConfig: Route[] = [
|
||||
{
|
||||
path: Routes.HOME,
|
||||
name: 'Home',
|
||||
name: t('Home'),
|
||||
text: t('Home'),
|
||||
element: <Home />,
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
path: Routes.TX,
|
||||
name: 'Txs',
|
||||
name: t('Txs'),
|
||||
text: t('Transactions'),
|
||||
element: <Txs />,
|
||||
children: [
|
||||
@@ -160,7 +165,7 @@ const routerConfig: Route[] = [
|
||||
},
|
||||
{
|
||||
path: Routes.BLOCKS,
|
||||
name: 'Blocks',
|
||||
name: t('Blocks'),
|
||||
text: t('Blocks'),
|
||||
element: <BlockPage />,
|
||||
children: [
|
||||
@@ -176,7 +181,7 @@ const routerConfig: Route[] = [
|
||||
},
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
name: 'Oracles',
|
||||
name: t('Oracles'),
|
||||
text: t('Oracles'),
|
||||
element: <OraclePage />,
|
||||
children: [
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
// Note: Long enough that there is a truncated output and a full output
|
||||
const pubKey =
|
||||
'67755549e43e95f0697f83b2bf419c6ccc18eee32a8a61b8ba6f59471b86fbef';
|
||||
const hash = '7416753A30622A9E24A06F0172D6C33A95186B36806D96345C6DC5A23FA3F283';
|
||||
const hash = '7416753a30622a9e24a06f0172d6c33a95186b36806d96345c6dc5a23fa3f283';
|
||||
const height = '52987';
|
||||
|
||||
const txData: BlockExplorerTransactionResult = {
|
||||
|
||||
@@ -3,3 +3,13 @@
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
Object.defineProperty(window, 'ResizeObserver', {
|
||||
writable: false,
|
||||
value: jest.fn().mockImplementation(() => ({
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
connect: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -2,3 +2,12 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.react-markdown-container a {
|
||||
color: #ff077f;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.react-markdown-container a:before {
|
||||
content: '🔗 ';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
export const trancheData = {
|
||||
'0': {
|
||||
tranche_id: 0,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 0,
|
||||
duration: 0,
|
||||
},
|
||||
'1': {
|
||||
tranche_id: 1,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 1670422330,
|
||||
duration: 1670453883,
|
||||
},
|
||||
'107': {
|
||||
tranche_id: 107,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 0,
|
||||
duration: 0,
|
||||
},
|
||||
'110': {
|
||||
tranche_id: 110,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 0,
|
||||
duration: 0,
|
||||
},
|
||||
'154': {
|
||||
tranche_id: 154,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 0,
|
||||
duration: 0,
|
||||
},
|
||||
'2': {
|
||||
tranche_id: 2,
|
||||
users: [
|
||||
'0x8716c75bb3abe54b959adc529e6355f0422454ed',
|
||||
'0xc704da0e96a6b910ffa8d3f9f667d3d35db8e5a1',
|
||||
'0xbb82d4d6e4381ae98f8c33629285867b54d91a03',
|
||||
'0x82ffe4ed818a6c7d4f0f044998611f24ec916c43',
|
||||
'0xb0594132540ebc14f17863f8296f4546a7afe4e7',
|
||||
'0x9015a3db9a922521007a9b495cafda14d8d4128e',
|
||||
'0x5426f7f717fdbe331bb5a99b908ad2fb75dff711',
|
||||
'0xf73e7ad8aa300a7f86bcc4d55e6c4ea25546e057',
|
||||
],
|
||||
initial_balance: 111300000000000000000,
|
||||
current_balance: 111297025049610000000,
|
||||
cliff_start: 1677578461,
|
||||
duration: 15209600,
|
||||
},
|
||||
'3': {
|
||||
tranche_id: 3,
|
||||
users: [
|
||||
'0xbb82d4d6e4381ae98f8c33629285867b54d91a03',
|
||||
'0x82ffe4ed818a6c7d4f0f044998611f24ec916c43',
|
||||
'0xb0594132540ebc14f17863f8296f4546a7afe4e7',
|
||||
'0x9015a3db9a922521007a9b495cafda14d8d4128e',
|
||||
'0x77a0b7e247b7b8ec99e068a24c401783d6c4dfff',
|
||||
'0x5426f7f717fdbe331bb5a99b908ad2fb75dff711',
|
||||
'0xc704da0e96a6b910ffa8d3f9f667d3d35db8e5a1',
|
||||
],
|
||||
initial_balance: 21000000000000000000,
|
||||
current_balance: 21000000000000000000,
|
||||
cliff_start: 1677578461,
|
||||
duration: 18628800,
|
||||
},
|
||||
'66': {
|
||||
tranche_id: 66,
|
||||
users: [],
|
||||
initial_balance: 0,
|
||||
current_balance: 0,
|
||||
cliff_start: 0,
|
||||
duration: 0,
|
||||
},
|
||||
};
|
||||
@@ -22,7 +22,7 @@
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.ETH.value",
|
||||
"name": "prices.BTC.value",
|
||||
"type": "TYPE_INTEGER",
|
||||
"numberDecimalPlaces": "0"
|
||||
},
|
||||
@@ -38,30 +38,34 @@
|
||||
}
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementPriceProperty": "prices.BTC.value",
|
||||
"settlementDataProperty": "prices.BTC.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"lpPriceRange": "11",
|
||||
"instrument": {
|
||||
"code": "Token.24h",
|
||||
"future": {
|
||||
"quoteName": "fBTC",
|
||||
"dataSourceSpecForSettlementData": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.BTC.value",
|
||||
"type": "TYPE_INTEGER"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
|
||||
"value": "1648684800000000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementDataProperty": "prices.BTC.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
|
||||
"priceMonitoringParameters": {
|
||||
"triggers": [
|
||||
{
|
||||
"horizon": "43200",
|
||||
"probability": "0.9999999",
|
||||
"auctionExtension": "600"
|
||||
}
|
||||
]
|
||||
},
|
||||
"logNormal": {
|
||||
"tau": 0.0001140771161,
|
||||
"riskAversionParameter": 0.001,
|
||||
"params": {
|
||||
"mu": 0,
|
||||
"r": 0.016,
|
||||
"sigma": 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,76 @@
|
||||
{
|
||||
"lpPriceRange": "10",
|
||||
"instrument": {
|
||||
"code": "TEST.24h",
|
||||
"future": {
|
||||
"quoteName": "fUSDC",
|
||||
"settlementDataDecimals": 5,
|
||||
"dataSourceSpecForSettlementData": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.ETH.value",
|
||||
"type": "TYPE_INTEGER"
|
||||
},
|
||||
"conditions": [
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.ETH.value",
|
||||
"type": "TYPE_INTEGER",
|
||||
"numberDecimalPlaces": "0"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
|
||||
"value": "1648684800000000000"
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementPriceProperty": "prices.ETH.value",
|
||||
"settlementDataProperty": "prices.ETH.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
|
||||
"metadata": [
|
||||
"sector:energy",
|
||||
"sector:food",
|
||||
"source:docs.vega.xyz",
|
||||
"test:update"
|
||||
],
|
||||
"priceMonitoringParameters": {
|
||||
"triggers": [
|
||||
{
|
||||
@@ -66,6 +80,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"liquidityMonitoringParameters": {
|
||||
"targetStakeParameters": {
|
||||
"timeWindow": "3600",
|
||||
"scalingFactor": 10
|
||||
},
|
||||
"triggeringRatio": "0.7",
|
||||
"auctionExtension": "1"
|
||||
},
|
||||
"logNormal": {
|
||||
"tau": 0.0001140771161,
|
||||
"riskAversionParameter": 0.001,
|
||||
|
||||
@@ -26,6 +26,10 @@ const enactmentDeadlineError =
|
||||
'[data-testid="enactment-before-voting-deadline"]';
|
||||
const proposalDownloadBtn = '[data-testid="proposal-download-json"]';
|
||||
const feedbackError = '[data-testid="Error"]';
|
||||
const viewProposalBtn = 'view-proposal-btn';
|
||||
const liquidityVoteStatus = 'liquidity-votes-status';
|
||||
const tokenVoteStatus = 'token-votes-status';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
const proposalTimeout = { timeout: 14000 };
|
||||
|
||||
@@ -45,6 +49,7 @@ context(
|
||||
{ tags: '@slow' },
|
||||
function () {
|
||||
before('connect wallets and set approval limit', function () {
|
||||
cy.createMarket();
|
||||
cy.visit('/');
|
||||
cy.vega_wallet_set_specified_approval_amount('1000');
|
||||
});
|
||||
@@ -65,7 +70,7 @@ context(
|
||||
// 3002-PROP-007
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
|
||||
cy.get(proposalParameterSelect).find('option').should('have.length', 116);
|
||||
cy.get(proposalParameterSelect).find('option').should('have.length', 117);
|
||||
cy.get(proposalParameterSelect).select(
|
||||
// 3007-PNEC-002
|
||||
'governance_proposal_asset_minEnact'
|
||||
@@ -175,9 +180,8 @@ context(
|
||||
cy.get(enactmentDeadlineError).should('not.exist');
|
||||
});
|
||||
|
||||
// Skipping because unclear what the required json is yet for new market proposal, will update once docs have been updated
|
||||
// 3003-todo-PMAN-001
|
||||
it.skip('Able to submit valid new market proposal', function () {
|
||||
// 3003-PMAN-001
|
||||
it('Able to submit valid new market proposal', function () {
|
||||
cy.go_to_make_new_proposal(governanceProposalType.NEW_MARKET);
|
||||
cy.get(newProposalTitle).type('Test new market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
@@ -199,6 +203,7 @@ context(
|
||||
cy.get(newProposalTitle).type('Test new market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.fixture('/proposals/new-market').then((newMarketProposal) => {
|
||||
newMarketProposal.invalid = 'I am an invalid field';
|
||||
let newMarketPayload = JSON.stringify(newMarketProposal);
|
||||
cy.get(newProposalTerms).type(newMarketPayload, {
|
||||
parseSpecialCharSequences: false,
|
||||
@@ -209,11 +214,66 @@ context(
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should(
|
||||
'have.text',
|
||||
'Invalid params: the transaction is malformed'
|
||||
'Invalid params: the transaction is not a valid Vega command: unknown field "invalid" in vega.NewMarket'
|
||||
);
|
||||
});
|
||||
|
||||
it.skip('Able to submit update market proposal', function () {
|
||||
// Will fail if run after 'Able to submit update market proposal and vote for proposal'
|
||||
// 3002-PROP-022
|
||||
it('Unable to submit update market proposal without equity-like share in the market', function () {
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal - rejected');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.get(proposalMarketSelect).select('Test market 1');
|
||||
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
|
||||
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
|
||||
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
|
||||
parseSpecialCharSequences: false,
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
|
||||
cy.getByTestId('dialog-content')
|
||||
.find('p')
|
||||
.should('have.text', 'PROPOSAL_ERROR_INSUFFICIENT_EQUITY_LIKE_SHARE');
|
||||
cy.ensure_specified_unstaked_tokens_are_associated('1');
|
||||
});
|
||||
|
||||
// 3002-PROP-020
|
||||
it('Unable to submit update market proposal without minimum amount of tokens', function () {
|
||||
cy.vega_wallet_teardown();
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
'fUSDC',
|
||||
'1000000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal - rejected');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.get(proposalMarketSelect).select('Test market 1');
|
||||
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
|
||||
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
|
||||
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
|
||||
parseSpecialCharSequences: false,
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should(
|
||||
'have.text',
|
||||
'Network error: the network blocked the transaction through the spam protection: party has insufficient associated governance tokens in their staking account to submit proposal request (ABCI code 89)'
|
||||
);
|
||||
});
|
||||
|
||||
// 3001-VOTE-092
|
||||
it('Able to submit update market proposal and vote for proposal', function () {
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
'fUSDC',
|
||||
'1000000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
@@ -222,6 +282,10 @@ context(
|
||||
cy.get('dd').eq(0).should('have.text', 'Test market 1');
|
||||
cy.get('dd').eq(1).should('have.text', 'TEST.24h');
|
||||
cy.get('dd').eq(2).should('not.be.empty');
|
||||
cy.get('dd').eq(2).invoke('text').as('EnactedMarketId');
|
||||
});
|
||||
cy.get('@EnactedMarketId').then((marketId) => {
|
||||
cy.VegaWalletSubmitLiquidityProvision(marketId, '1');
|
||||
});
|
||||
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
|
||||
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
|
||||
@@ -232,6 +296,34 @@ context(
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.wait_for_proposal_submitted();
|
||||
cy.navigate_to('proposals');
|
||||
cy.get('@EnactedMarketId').then((marketId) => {
|
||||
cy.contains(marketId)
|
||||
.parentsUntil(proposalListItem)
|
||||
.within(() => {
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(liquidityVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to fail'
|
||||
);
|
||||
cy.getByTestId(tokenVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to fail'
|
||||
);
|
||||
cy.vote_for_proposal('for');
|
||||
cy.getByTestId(liquidityVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.getByTestId(tokenVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.get_proposal_information_from_table('Expected to pass')
|
||||
.contains('👍 by Token vote')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001
|
||||
@@ -298,7 +390,7 @@ context(
|
||||
.parentsUntil(proposalListItem)
|
||||
.within(() => {
|
||||
cy.get(proposalDetails).should('contain.text', assetId); // 3001-VOTE-029
|
||||
cy.getByTestId('view-proposal-btn').click();
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
});
|
||||
cy.get_proposal_information_from_table('Proposed enactment') // 3001-VOTE-044
|
||||
|
||||
@@ -14,13 +14,14 @@ const stakeShare = '[data-testid="stake-percentage"]';
|
||||
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
|
||||
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
|
||||
const vegaWalletUnstakedBalance =
|
||||
'[data-testid="vega-wallet-balance-unstaked"]';
|
||||
'[data-testid="vega-wallet-balance-unstaked"]:visible';
|
||||
const vegaWalletStakedBalances =
|
||||
'[data-testid="vega-wallet-balance-staked-validators"]';
|
||||
const ethWalletAssociatedBalances =
|
||||
'[data-testid="eth-wallet-associated-balances"]';
|
||||
const ethWalletTotalAssociatedBalance = '[data-testid="currency-locked"]';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const ethWalletTotalAssociatedBalance =
|
||||
'[data-testid="currency-locked"]:visible';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]:visible';
|
||||
const vegaWallet = '[data-testid="vega-wallet"]';
|
||||
const partValidatorId = '…';
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
|
||||
@@ -1,60 +1,10 @@
|
||||
const navSection = 'nav';
|
||||
const navSupply = '[href="/token/tranches"]';
|
||||
const navToken = '[href="/token"]';
|
||||
const navStaking = '[href="/validators"]';
|
||||
const navRewards = '[href="/rewards"]';
|
||||
const navWithdraw = '[href="/token/withdraw"]';
|
||||
const navGovernance = '[href="/proposals"]';
|
||||
const navRedeem = '[href="/token/redeem"]';
|
||||
|
||||
context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
before('visit token home page', function () {
|
||||
cy.visit('/');
|
||||
cy.get('nav', { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
before('wait for page to load', function () {
|
||||
cy.get(navSection, { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('Navigation tabs', function () {
|
||||
it('should have proposals tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navGovernance).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have validators tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navStaking).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have rewards tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navRewards).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Token dropdown', function () {
|
||||
before('click on token dropdown', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.getByTestId('state-trigger').realClick();
|
||||
});
|
||||
});
|
||||
it('should have token dropdown', function () {
|
||||
cy.get(navToken).should('be.visible');
|
||||
});
|
||||
it('should have supply & vesting dropdown', function () {
|
||||
cy.get(navSupply).should('be.visible');
|
||||
});
|
||||
it('should have withdraw dropdown', function () {
|
||||
cy.get(navWithdraw).should('be.visible');
|
||||
});
|
||||
it('should have redeem dropdown', function () {
|
||||
cy.get(navRedeem).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Links and buttons', function () {
|
||||
it('should have link for proposal page', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
context(
|
||||
'Landing pages - verifies required elements',
|
||||
{ tags: '@smoke' },
|
||||
() => {
|
||||
const navbar = 'nav .navbar';
|
||||
const mobileNav = '[data-testid="menu-drawer"]';
|
||||
|
||||
const topLevelLinks = [
|
||||
{
|
||||
name: 'Proposals',
|
||||
selector: '[href="/proposals"]',
|
||||
tests: () => {
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
const governanceDocsUrl = 'https://vega.xyz/governance';
|
||||
const proposalDocumentationLink =
|
||||
'[data-testid="proposal-documentation-link"]';
|
||||
// 3001-VOTE-001
|
||||
cy.get(proposalDocumentationLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Find out more about Vega governance')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', governanceDocsUrl);
|
||||
|
||||
// 3002-PROP-001
|
||||
cy.request(governanceDocsUrl)
|
||||
.its('body')
|
||||
.then((body) => {
|
||||
if (!body.includes('Govern the network')) {
|
||||
assert.include(
|
||||
body,
|
||||
'Govern the network',
|
||||
`Checking that governance link destination includes 'Govern the network' text`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see button for - new proposal', function () {
|
||||
// 3001-VOTE-002
|
||||
const newProposalLink = '[data-testid="new-proposal-link"]';
|
||||
cy.get(newProposalLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'New proposal')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', '/proposals/propose');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Validators',
|
||||
selector: '[href="/validators"]',
|
||||
tests: () => {
|
||||
it('Should have Staking Guide link visible', function () {
|
||||
// 2001-STKE-003
|
||||
cy.get('[data-testid="staking-guide-link"]')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Read more about staking on Vega')
|
||||
.and(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://docs.vega.xyz/mainnet/concepts/vega-chain/#staking-on-vega'
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Rewards',
|
||||
selector: '[href="/rewards"]',
|
||||
header: 'Rewards and fees',
|
||||
tests: () => {
|
||||
it('should have epoch warning', () => {
|
||||
cy.get('[data-testid="callout"]')
|
||||
.should('be.visible')
|
||||
.and(
|
||||
'have.text',
|
||||
'Rewards are credited 5 minutes after the epoch ends.This delay is set by a network parameter'
|
||||
);
|
||||
});
|
||||
it('should have toggle for seeing total vs individual rewards', () => {
|
||||
cy.get('[data-testid="epoch-reward-view-toggle-total"]').should(
|
||||
'be.visible'
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const secondLevelLinks = [
|
||||
{
|
||||
trigger: true,
|
||||
name: 'Token',
|
||||
selector: '[data-testid="state-trigger"]',
|
||||
},
|
||||
{
|
||||
name: 'Token',
|
||||
selector: '[href="/token"]',
|
||||
header: 'The $VEGA token',
|
||||
},
|
||||
{
|
||||
name: 'Supply & Vesting',
|
||||
selector: '[href="/token/tranches"]',
|
||||
header: 'Vesting tranches',
|
||||
},
|
||||
{
|
||||
name: 'Withdraw',
|
||||
selector: '[href="/token/withdraw"]',
|
||||
header: 'Withdrawals',
|
||||
tests: () => {
|
||||
it('should have connect Vega wallet button', function () {
|
||||
cy.get('[data-testid="connect-to-vega-wallet-btn"]')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Redeem',
|
||||
selector: '[href="/token/redeem"]',
|
||||
header: 'Vesting',
|
||||
tests: () => {
|
||||
// 1005-VEST-018
|
||||
it('should have connect Eth wallet button', function () {
|
||||
cy.get('[data-testid="connect-to-eth-btn"]')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Ethereum wallet');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Associate',
|
||||
selector: '[href="/token/associate"]',
|
||||
header: 'Associate $VEGA tokens with Vega Key',
|
||||
},
|
||||
{
|
||||
name: 'Disassociate',
|
||||
selector: '[href="/token/disassociate"]',
|
||||
header: 'Disassociate $VEGA tokens from a Vega key',
|
||||
},
|
||||
];
|
||||
|
||||
const expand = () => {
|
||||
const trigger = secondLevelLinks.find((l) => l.trigger).selector;
|
||||
cy.get(trigger).then((el) => {
|
||||
if (el.attr('aria-expanded') === 'false') {
|
||||
el.trigger('click');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const collapse = () => {
|
||||
const trigger = secondLevelLinks.find((l) => l.trigger).selector;
|
||||
cy.get(trigger).then((el) => {
|
||||
if (el.attr('aria-expanded') === 'true') {
|
||||
el.trigger('click');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const ensureHeader = (text) => {
|
||||
cy.get('main header h1').should('have.text', text);
|
||||
};
|
||||
|
||||
before(() => {
|
||||
// goes to HOME
|
||||
cy.visit('/');
|
||||
// and waits for it to load
|
||||
cy.get(navbar, { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('Navigation (desktop)', () => {
|
||||
for (const { name, selector } of topLevelLinks) {
|
||||
it(`should have ${name} nav link`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, selector, trigger } of secondLevelLinks) {
|
||||
it(`should have ${name} ${
|
||||
trigger ? 'as trigger button' : ''
|
||||
} second level nav link`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
if (trigger) cy.get(selector).click();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
after(() => {
|
||||
collapse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigation (mobile)', () => {
|
||||
beforeEach(() => {
|
||||
// iphone xr
|
||||
cy.viewport(414, 896);
|
||||
});
|
||||
|
||||
it('should have burger button', () => {
|
||||
cy.get('[data-testid="button-menu-drawer"]').should('be.visible');
|
||||
cy.get('[data-testid="button-menu-drawer"]').click();
|
||||
cy.get(mobileNav).should('be.visible');
|
||||
});
|
||||
|
||||
for (const { name, selector } of topLevelLinks) {
|
||||
it(`should have ${name} nav link`, () => {
|
||||
cy.get(mobileNav).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, selector, trigger } of secondLevelLinks) {
|
||||
it(`should have ${name} ${
|
||||
trigger ? 'as trigger button' : ''
|
||||
} second level nav link`, () => {
|
||||
cy.get(mobileNav).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
after(() => {
|
||||
cy.get('[data-testid="button-menu-drawer"]').click();
|
||||
cy.viewport(
|
||||
Cypress.config('viewportWidth'),
|
||||
Cypress.config('viewportHeight')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Elements', () => {
|
||||
for (const { name, selector, header, tests } of topLevelLinks) {
|
||||
describe(`${name} page`, () => {
|
||||
it(`navigates to ${name}`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
cy.log(`goes to ${name}`);
|
||||
cy.get(selector).click();
|
||||
cy.log(`ensures ${name} is highlighted`);
|
||||
cy.get(selector).should('have.attr', 'aria-current');
|
||||
});
|
||||
});
|
||||
it('displays header', () => {
|
||||
ensureHeader(header || name);
|
||||
});
|
||||
|
||||
if (tests) tests.apply(this);
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, selector, header, tests } of secondLevelLinks.filter(
|
||||
(l) => !l.trigger
|
||||
)) {
|
||||
describe(`${name} page`, () => {
|
||||
it(`navigates to ${name}`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
expand();
|
||||
cy.log(`goes to ${name}`);
|
||||
cy.get(selector).click();
|
||||
expand();
|
||||
cy.log(`ensures ${name} is highlighted`);
|
||||
cy.get(selector).should('have.attr', 'aria-current');
|
||||
});
|
||||
});
|
||||
it('displays header', () => {
|
||||
ensureHeader(header || name);
|
||||
});
|
||||
|
||||
if (tests) tests.apply(this);
|
||||
});
|
||||
}
|
||||
|
||||
after(() => {
|
||||
collapse();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,68 +0,0 @@
|
||||
const proposalDocumentationLink = '[data-testid="proposal-documentation-link"]';
|
||||
const newProposalButton = '[data-testid="new-proposal-link"]';
|
||||
const newProposalLink = '[data-testid="new-proposal-link"]';
|
||||
const governanceDocsUrl = 'https://vega.xyz/governance';
|
||||
const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]';
|
||||
|
||||
context(
|
||||
'Governance Page - verify elements on page',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
before('navigate to governance page', function () {
|
||||
cy.visit('/').navigate_to('proposals');
|
||||
});
|
||||
|
||||
describe('with no network change proposals', function () {
|
||||
it('should have governance tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('proposals');
|
||||
});
|
||||
|
||||
it('should have GOVERNANCE header visible', function () {
|
||||
cy.verify_page_header('Proposals');
|
||||
});
|
||||
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
// 3001-VOTE-001
|
||||
cy.get(proposalDocumentationLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Find out more about Vega governance')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', governanceDocsUrl);
|
||||
|
||||
// 3002-PROP-001
|
||||
cy.request(governanceDocsUrl)
|
||||
.its('body')
|
||||
.then((body) => {
|
||||
if (!body.includes('Govern the network')) {
|
||||
assert.include(
|
||||
body,
|
||||
'Govern the network',
|
||||
`Checking that governance link destination includes 'Govern the network' text`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see button for - new proposal', function () {
|
||||
// 3001-VOTE-002
|
||||
cy.get(newProposalLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'New proposal')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', '/proposals/propose');
|
||||
});
|
||||
|
||||
// Skipping this test for now, the new proposal button no longer takes a user directly
|
||||
// to a proposal form, instead it takes them to a page where they can select a proposal type.
|
||||
// Keeping this test here for now as it can be repurposed to test the new proposal forms.
|
||||
it.skip('should be able to see a connect wallet button - if vega wallet disconnected and new proposal button selected', function () {
|
||||
cy.get(newProposalButton).should('be.visible').click();
|
||||
cy.get(connectToVegaWalletButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
cy.navigate_to('proposals');
|
||||
cy.wait_for_spinner();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -50,7 +50,7 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
});
|
||||
|
||||
it('Able to disconnect via wallet', function () {
|
||||
cy.getByTestId('manage-vega-wallet').click();
|
||||
cy.get('aside [data-testid="manage-vega-wallet"]').click();
|
||||
cy.getByTestId('disconnect').click();
|
||||
cy.getByTestId(banner).should('not.exist');
|
||||
});
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
const viewToggle = '[data-testid="epoch-reward-view-toggle-total"]';
|
||||
const warning = '[data-testid="callout"]';
|
||||
|
||||
context(
|
||||
'Rewards Page - verify elements on page',
|
||||
{ tags: '@regression' },
|
||||
function () {
|
||||
before('navigate to rewards page', function () {
|
||||
cy.visit('/').navigate_to('rewards');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
it('should have REWARDS tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('rewards');
|
||||
});
|
||||
|
||||
it('should have rewards header visible', function () {
|
||||
cy.verify_page_header('Rewards and fees');
|
||||
});
|
||||
|
||||
it('should have epoch warning', function () {
|
||||
cy.get(warning)
|
||||
.should('be.visible')
|
||||
.and(
|
||||
'have.text',
|
||||
'Rewards are credited 5 minutes after the epoch ends.This delay is set by a network parameter'
|
||||
);
|
||||
});
|
||||
|
||||
it('should have toggle for seeing total vs individual rewards', function () {
|
||||
cy.get(viewToggle).should('be.visible');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -17,8 +17,7 @@ const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
|
||||
|
||||
context('Verify elements on Token page', { tags: '@smoke' }, function () {
|
||||
before('Visit token page', function () {
|
||||
cy.visit('/');
|
||||
cy.navigate_to('token');
|
||||
cy.visit('/token');
|
||||
});
|
||||
describe('THE $VEGA TOKEN table', function () {
|
||||
it('should have TOKEN ADDRESS', function () {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { trancheData } from '../../fixtures/mocks/tranches';
|
||||
|
||||
const tranches = trancheData;
|
||||
|
||||
context(
|
||||
'Tranches page - verify elements on the page',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
before('visit homepage', function () {
|
||||
cy.intercept('GET', '**/tranches/stats', { tranches });
|
||||
cy.visit('/token/tranches');
|
||||
});
|
||||
|
||||
// 1005-VEST-001
|
||||
// 1005-VEST-002
|
||||
it('Able to view tranches', function () {
|
||||
cy.getByTestId('tranche-item')
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('a')
|
||||
.should('have.text', 'Tranche 2') // 1005-VEST-003
|
||||
.and('have.attr', 'href', '/token/tranches/2');
|
||||
cy.get('span').eq(1).should('have.text', '111.30'); // 1005-VEST-005
|
||||
cy.contains('Unlocking starts') // 1005-VEST-008
|
||||
.parent()
|
||||
.should('contain.text', '28 Feb 2023');
|
||||
cy.contains('Fully unlocked')
|
||||
.parent()
|
||||
.should('contain.text', '23 Aug 2023');
|
||||
cy.getByTestId('progress-bar').should('exist');
|
||||
cy.getByTestId('currency-locked') // 1005-VEST-006
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('currency-unlocked') // 1005-VEST-007
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to see individual tranche data', function () {
|
||||
cy.get('[href="/token/tranches/2"]').click();
|
||||
cy.getByTestId('redeemed-tranche-tokens').within(() => {
|
||||
cy.get('span').eq(1).should('have.text', 0);
|
||||
});
|
||||
cy.getByTestId('key-value-table').within(() => {
|
||||
cy.getByTestId('link')
|
||||
.should('have.length', 8)
|
||||
.each((ethLink) => {
|
||||
cy.wrap(ethLink)
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', 'https://sepolia.etherscan.io/address/');
|
||||
});
|
||||
cy.getByTestId('redeem-link')
|
||||
.should('have.length', 8)
|
||||
.each((redeemLink) => {
|
||||
cy.wrap(redeemLink)
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/token/redeem/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to view tranches with less than 10 vega', function () {
|
||||
cy.navigate_to('supply');
|
||||
cy.getByTestId('show-all-tranches').click();
|
||||
cy.getByTestId('tranche-item')
|
||||
.should('have.length', 8)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get('a')
|
||||
.should('have.text', 'Tranche 0')
|
||||
.and('have.attr', 'href', '/token/tranches/0');
|
||||
cy.get('span').eq(1).should('have.text', '0.00');
|
||||
cy.contains('Unlocking starts')
|
||||
.parent()
|
||||
.should('contain.text', '01 Jan 1970');
|
||||
cy.contains('Fully unlocked')
|
||||
.parent()
|
||||
.should('contain.text', '01 Jan 1970');
|
||||
cy.getByTestId('progress-bar').should('exist');
|
||||
cy.getByTestId('currency-locked')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('currency-unlocked')
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,5 +1,4 @@
|
||||
/// <reference types="cypress" />
|
||||
const guideLink = '[data-testid="staking-guide-link"]';
|
||||
const validatorTitle = '[data-testid="validator-node-title"]';
|
||||
const validatorId = '[data-testid="validator-id"]';
|
||||
const validatorPubKey = '[data-testid="validator-public-key"]';
|
||||
@@ -25,31 +24,7 @@ const stakeNumberRegex = /^\d*\.?\d*$/;
|
||||
|
||||
context('Staking Page - verify elements on page', function () {
|
||||
before('navigate to staking page', function () {
|
||||
cy.visit('/').navigate_to('validators');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', { tags: '@smoke' }, function () {
|
||||
describe('description section', function () {
|
||||
it('Should have validators tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('validators');
|
||||
});
|
||||
|
||||
it('Should have validators ON VEGA header visible', function () {
|
||||
cy.verify_page_header('Validators');
|
||||
});
|
||||
|
||||
it('Should have Staking Guide link visible', function () {
|
||||
// 2001-STKE-003
|
||||
cy.get(guideLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Read more about staking on Vega')
|
||||
.and(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://docs.vega.xyz/mainnet/concepts/vega-chain/#staking-on-vega'
|
||||
);
|
||||
});
|
||||
});
|
||||
cy.visit('/validators');
|
||||
});
|
||||
|
||||
describe(
|
||||
|
||||
@@ -1,44 +1,111 @@
|
||||
const connectButton = '[data-testid="connect-to-eth-btn"]';
|
||||
const lockedTokensInVestingContract = '6,499,972.30';
|
||||
|
||||
context(
|
||||
'Vesting Page - verify elements on page',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
describe('with wallets disconnected', function () {
|
||||
before('navigate to vesting page', function () {
|
||||
cy.visit('/').navigate_to('vesting');
|
||||
});
|
||||
it('should have vesting tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('token');
|
||||
});
|
||||
|
||||
it('should have VESTING header visible', function () {
|
||||
cy.verify_page_header('Vesting');
|
||||
});
|
||||
|
||||
it('should have connect Eth wallet info', function () {
|
||||
cy.get(connectButton).should('be.visible');
|
||||
});
|
||||
|
||||
it('should have connect Eth wallet button', function () {
|
||||
cy.get(connectButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Ethereum wallet');
|
||||
});
|
||||
before('navigate to vesting page', function () {
|
||||
cy.visit('/token/redeem');
|
||||
});
|
||||
|
||||
describe('with eth wallet connected', function () {
|
||||
describe('With Eth wallet connected', function () {
|
||||
before('connect eth wallet', function () {
|
||||
cy.ethereum_wallet_connect();
|
||||
cy.visit('/');
|
||||
cy.getByTestId('view-connected-eth-btn').click();
|
||||
});
|
||||
|
||||
// 1005-VEST-001
|
||||
// 1005-VEST-002
|
||||
it('Able to view tranches', function () {
|
||||
cy.navigate_to('supply');
|
||||
cy.url().should('include', '/token/tranches');
|
||||
cy.get('h1').should('contain.text', 'Vesting tranches');
|
||||
// 1005-VEST-020 1005-VEST-021
|
||||
it('Tokens in vesting contract for eth wallet is displayed on wallet window', function () {
|
||||
cy.getByTestId('vega-in-vesting-contract').within(() => {
|
||||
cy.getByTestId('currency-title')
|
||||
.should('contain.text', 'VEGA')
|
||||
.and('contain.text', 'In vesting contract');
|
||||
cy.get('[data-testid="currency-value"]:visible').should(
|
||||
'have.text',
|
||||
lockedTokensInVestingContract
|
||||
);
|
||||
cy.get('[data-testid="currency-locked"]:visible').should(
|
||||
'have.text',
|
||||
lockedTokensInVestingContract
|
||||
);
|
||||
cy.get('[data-testid="currency-unlocked"]:visible').should(
|
||||
'have.text',
|
||||
'0.00'
|
||||
);
|
||||
});
|
||||
});
|
||||
// 1005-VEST-022 1005-VEST-023
|
||||
it('Tokens amount displayed in vesting page', function () {
|
||||
cy.getByTestId(
|
||||
'redemption-description',
|
||||
Cypress.env('txTimeout')
|
||||
).should('exist');
|
||||
const redemptionText =
|
||||
'The connected Ethereum wallet (0xEe7D…d94F) has 6,499,972.30 $VEGA tokens in 1 tranche(s) of the vesting contract.';
|
||||
|
||||
cy.getByTestId('redemption-description').should(
|
||||
'have.text',
|
||||
redemptionText
|
||||
);
|
||||
cy.getByTestId('vesting-table').within(() => {
|
||||
cy.getByTestId('key-value-table-row')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('dt').should('have.text', 'Vesting VEGA');
|
||||
cy.get('dd').should('have.text', lockedTokensInVestingContract);
|
||||
});
|
||||
cy.getByTestId('key-value-table-row')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('dt').should('have.text', 'Locked');
|
||||
cy.get('dd').should('have.text', lockedTokensInVestingContract);
|
||||
});
|
||||
cy.getByTestId('key-value-table-row')
|
||||
.eq(2)
|
||||
.within(() => {
|
||||
cy.get('dt').should('have.text', 'Unlocked');
|
||||
cy.get('dd').should('have.text', '0.00');
|
||||
});
|
||||
cy.getByTestId('key-value-table-row')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.get('dt').should('have.text', 'Associated');
|
||||
cy.get('dd').should('have.text', '0.00');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 1005-VEST-024 1005-VEST-025 1005-VEST-026 1005-VEST-036 1005-VEST-037
|
||||
it('Tokens locked in individual tranches are displayed', function () {
|
||||
cy.getByTestId('tranche-table-footer').should(
|
||||
'have.text',
|
||||
'All the tokens in this tranche are locked and must be assigned to a tranche before they can be redeemed.'
|
||||
);
|
||||
cy.getByTestId('tranche-item').within(() => {
|
||||
cy.get('a')
|
||||
.should('have.text', 'Tranche 0')
|
||||
.and('have.attr', 'href', '/token/tranches/0');
|
||||
cy.get('span')
|
||||
.eq(1)
|
||||
.should('have.text', lockedTokensInVestingContract);
|
||||
cy.contains('Unlocking starts') // 1005-VEST-008
|
||||
.parent()
|
||||
.should('contain.text', '01 Jan 1970');
|
||||
cy.contains('Fully unlocked')
|
||||
.parent()
|
||||
.should('contain.text', '01 Jan 1970');
|
||||
cy.getByTestId('progress-bar').should('exist');
|
||||
cy.getByTestId('currency-locked') // 1005-VEST-006
|
||||
.should('have.text', lockedTokensInVestingContract);
|
||||
cy.getByTestId('currency-unlocked') // 1005-VEST-007
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.getByTestId('tranche-item-footer').should(
|
||||
'have.text',
|
||||
'All the tokens in this tranche are locked and can not be redeemed yet.'
|
||||
);
|
||||
});
|
||||
cy.connectVegaWallet();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
const walletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const walletContainer = 'aside [data-testid="ethereum-wallet"]';
|
||||
const walletHeader = '[data-testid="wallet-header"] h1';
|
||||
const connectToEthButton = '[data-testid="connect-to-eth-wallet-button"]';
|
||||
const connectToEthButton =
|
||||
'[data-testid="connect-to-eth-wallet-button"]:visible';
|
||||
const connectorList = '[data-testid="web3-connector-list"]';
|
||||
const associate = '[href="/token/associate"]';
|
||||
const disassociate = '[href="/token/disassociate"]';
|
||||
const disconnect = '[data-testid="disconnect-from-eth-wallet-button"]';
|
||||
const accountNo = '[data-testid="ethereum-account-truncated"]';
|
||||
const currencyTitle = '[data-testid="currency-title"]';
|
||||
const currencyValue = '[data-testid="currency-value"]';
|
||||
const vegaInVesting = '[data-testid="vega-in-vesting-contract"]';
|
||||
const vegaInWallet = '[data-testid="vega-in-wallet"]';
|
||||
const progressBar = '[data-testid="progress-bar"]';
|
||||
const currencyLocked = '[data-testid="currency-locked"]';
|
||||
const currencyUnlocked = '[data-testid="currency-unlocked"]';
|
||||
const currencyTitle = '[data-testid="currency-title"]:visible';
|
||||
const currencyValue = '[data-testid="currency-value"]:visible';
|
||||
const vegaInVesting = '[data-testid="vega-in-vesting-contract"]:visible';
|
||||
const vegaInWallet = '[data-testid="vega-in-wallet"]:visible';
|
||||
const progressBar = '[data-testid="progress-bar"]:visible';
|
||||
const currencyLocked = '[data-testid="currency-locked"]:visible';
|
||||
const currencyUnlocked = '[data-testid="currency-unlocked"]:visible';
|
||||
const dialog = '[role="dialog"]';
|
||||
const dialogHeader = '[data-testid="dialog-title"]';
|
||||
const dialogCloseBtn = '[data-testid="dialog-close"]';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
|
||||
const walletContainer = '[data-testid="vega-wallet"]';
|
||||
const walletContainer = 'aside [data-testid="vega-wallet"]';
|
||||
const walletHeader = '[data-testid="wallet-header"] h1';
|
||||
const connectButton = '[data-testid="connect-vega-wallet"]';
|
||||
const getVegaLink = '[data-testid="link"]';
|
||||
@@ -14,7 +14,6 @@ const restWallet = '#wallet';
|
||||
const restPassphrase = '#passphrase';
|
||||
const restConnectBtn = '[type="submit"]';
|
||||
const accountNo = '[data-testid="vega-account-truncated"]';
|
||||
const walletName = '[data-testid="wallet-name"]';
|
||||
const currencyTitle = '[data-testid="currency-title"]';
|
||||
const currencyValue = '[data-testid="currency-value"]';
|
||||
const vegaUnstaked = '[data-testid="vega-wallet-balance-unstaked"] .text-right';
|
||||
@@ -28,44 +27,24 @@ const vegaWalletCurrencyTitle = '[data-testid="currency-title"]';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
|
||||
const faucetAssets = {
|
||||
BTCFake: 'fBTC',
|
||||
DAIFake: 'fDAI',
|
||||
EUROFake: 'fEURO',
|
||||
USDCFake: 'fUSDC',
|
||||
};
|
||||
|
||||
context(
|
||||
'Vega Wallet - verify elements on widget',
|
||||
{ tags: '@regression' },
|
||||
function () {
|
||||
before('visit token home page', function () {
|
||||
() => {
|
||||
before('visit token home page', () => {
|
||||
cy.visit('/');
|
||||
cy.get(walletContainer, { timeout: 60000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
before('wait for widget to load', function () {
|
||||
cy.get(walletContainer, { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
it('should have VEGA WALLET header visible', function () {
|
||||
describe('with wallets disconnected', () => {
|
||||
it('should have required elements visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(walletHeader)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Vega Wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Connect Vega button visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(connectButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet to use associated $VEGA');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Get a Vega wallet link visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(getVegaLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Get a Vega wallet')
|
||||
@@ -74,14 +53,14 @@ context(
|
||||
});
|
||||
});
|
||||
|
||||
describe('when connect button clicked', function () {
|
||||
before('click connect vega wallet button', function () {
|
||||
describe('when connect button clicked', () => {
|
||||
before('click connect vega wallet button', () => {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(connectButton).click();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Connect Vega header visible', function () {
|
||||
it('should have Connect Vega header visible', () => {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(walletDialogHeader)
|
||||
.should('be.visible')
|
||||
@@ -175,14 +154,6 @@ context(
|
||||
}
|
||||
);
|
||||
|
||||
it.skip('should have wallet name visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(walletName)
|
||||
.should('be.visible')
|
||||
.and('have.text', `${Cypress.env('vegaWalletName')} key 1`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Vega Associated currency title visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(currencyTitle)
|
||||
@@ -299,113 +270,71 @@ context(
|
||||
});
|
||||
|
||||
// 2002-SINC-016
|
||||
describe('when assets exist in vegawallet', function () {
|
||||
before('send-faucet assets to connected vega wallet', function () {
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.USDCFake,
|
||||
'1000000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.BTCFake,
|
||||
'600000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.EUROFake,
|
||||
'800000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.DAIFake,
|
||||
'200000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
describe('Vega wallet with assets', function () {
|
||||
const assets = [
|
||||
{
|
||||
id: 'fUSDC',
|
||||
name: 'USDC (fake)',
|
||||
amount: '1000000',
|
||||
expectedAmount: '10.00',
|
||||
},
|
||||
{
|
||||
id: 'fDAI',
|
||||
name: 'DAI (fake)',
|
||||
amount: '200000',
|
||||
expectedAmount: '2.00',
|
||||
},
|
||||
{
|
||||
id: 'fBTC',
|
||||
name: 'BTC (fake)',
|
||||
amount: '600000',
|
||||
expectedAmount: '6.00',
|
||||
},
|
||||
{
|
||||
id: 'fEURO',
|
||||
name: 'EURO (fake)',
|
||||
amount: '800000',
|
||||
expectedAmount: '8.00',
|
||||
},
|
||||
];
|
||||
|
||||
before('faucet assets to connected vega wallet', function () {
|
||||
for (const { id, amount } of assets) {
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
id,
|
||||
amount,
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
}
|
||||
cy.reload();
|
||||
cy.wait_for_spinner();
|
||||
cy.connectVegaWallet();
|
||||
cy.ethereum_wallet_connect();
|
||||
});
|
||||
|
||||
it('should see fUSDC assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.USDCFake, name: 'USDC (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
for (const { id, name, expectedAmount } of assets) {
|
||||
it(`should see ${id} within vega wallet`, () => {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within((el) => {
|
||||
const value = parseFloat(el.text());
|
||||
cy.wrap(value).should('be.gte', parseFloat(expectedAmount));
|
||||
});
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(id)
|
||||
.parent()
|
||||
.contains(name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should see fBTC assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.BTCFake, name: 'BTC (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within(() => cy.contains_exactly('6.00').should('be.visible'));
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
});
|
||||
});
|
||||
|
||||
it('should see fEURO assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.EUROFake, name: 'EURO (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within(() => cy.contains_exactly('8.00').should('be.visible'));
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
});
|
||||
});
|
||||
|
||||
it('should see fDAI assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.DAIFake, name: 'DAI (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within(() => cy.contains_exactly('2.00').should('be.visible'));
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
const connectToVegaBtn = '[data-testid="connect-to-vega-wallet-btn"]';
|
||||
|
||||
context(
|
||||
'Withdraw Page - verify elements on page',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
before('navigate to withdrawals page', function () {
|
||||
cy.visit('/').navigate_to('withdraw');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
it('should have withdraw tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('token');
|
||||
});
|
||||
|
||||
it('should have WITHDRAW header visible', function () {
|
||||
cy.verify_page_header('Withdrawals');
|
||||
});
|
||||
|
||||
it('should have connect Vega wallet button', function () {
|
||||
cy.get(connectToVegaBtn)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -22,25 +22,32 @@ const navigation = {
|
||||
};
|
||||
|
||||
const topLevelRoutes = ['proposals', 'validators', 'rewards'];
|
||||
const tokenDropDown = 'state-trigger';
|
||||
|
||||
Cypress.Commands.add('navigate_to', (page) => {
|
||||
const tokenDropDown = 'state-trigger';
|
||||
|
||||
if (!topLevelRoutes.includes(page)) {
|
||||
cy.getByTestId(tokenDropDown, { timeout: 10000 }).click();
|
||||
cy.getByTestId('token-dropdown').within(() => {
|
||||
cy.get(navigation[page]).click();
|
||||
// FIXME: Timeout madness
|
||||
cy.getByTestId(tokenDropDown, { timeout: 60000 }).eq(0).click();
|
||||
cy.get('[data-testid="token-dropdown"]:visible').within(() => {
|
||||
cy.get(navigation[page]).eq(0).click();
|
||||
});
|
||||
} else {
|
||||
return cy.get(navigation.section, { timeout: 10000 }).within(() => {
|
||||
cy.get(navigation[page]).click();
|
||||
cy.get(navigation[page]).eq(0).click();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Cypress.Commands.add('verify_tab_highlighted', (page) => {
|
||||
return cy.get(navigation.section).within(() => {
|
||||
cy.get(navigation[page]).should('have.attr', 'aria-current');
|
||||
if (!topLevelRoutes.includes(page)) {
|
||||
cy.getByTestId(tokenDropDown, { timeout: 10000 }).eq(0).click();
|
||||
cy.get('[data-testid="token-dropdown"]:visible').within(() => {
|
||||
cy.get(navigation[page]).should('have.attr', 'aria-current');
|
||||
});
|
||||
} else {
|
||||
cy.get(navigation[page]).should('have.attr', 'aria-current');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,7 +66,7 @@ export function associateTokenStartOfTests() {
|
||||
cy.highlight(`Associating tokens for first time`);
|
||||
cy.ethereum_wallet_connect();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('[href="/token/associate"]').first().click();
|
||||
cy.get('[href="/token/associate"]:visible').first().click();
|
||||
cy.getByTestId('associate-radio-wallet', { timeout: 30000 }).click();
|
||||
cy.getByTestId('token-amount-input', epochTimeout).type('1');
|
||||
cy.getByTestId('token-input-submit-button', txTimeout)
|
||||
|
||||
@@ -82,9 +82,10 @@ Cypress.Commands.add(
|
||||
Cypress.Commands.add(
|
||||
'get_submitted_proposal_from_proposal_list',
|
||||
(proposalTitle) => {
|
||||
cy.get_proposal_id_from_list(proposalTitle);
|
||||
cy.get('@proposalIdText').then((proposalId) => {
|
||||
return cy.get(`#${proposalId}`);
|
||||
cy.get_proposal_id_from_list(proposalTitle).then(() => {
|
||||
cy.get('@proposalIdText').then((proposalId) => {
|
||||
return cy.get(`#${proposalId}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,10 +3,10 @@ const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
|
||||
const tokenInputApprove = '[data-testid="token-input-approve-button"]';
|
||||
const addStakeRadioButton = '[data-testid="add-stake-radio"]';
|
||||
const removeStakeRadioButton = '[data-testid="remove-stake-radio"]';
|
||||
const ethWalletAssociateButton = '[href="/token/associate"]';
|
||||
const ethWalletDissociateButton = '[href="/token/disassociate"]';
|
||||
const ethWalletAssociateButton = '[href="/token/associate"]:visible';
|
||||
const ethWalletDissociateButton = '[href="/token/disassociate"]:visible';
|
||||
const vegaWalletUnstakedBalance =
|
||||
'[data-testid="vega-wallet-balance-unstaked"]';
|
||||
'[data-testid="vega-wallet-balance-unstaked"]:visible';
|
||||
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
|
||||
const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]';
|
||||
const associateContractRadioButton = '[data-testid="associate-radio-contract"]';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const connectToEthButton = '[data-testid="connect-to-eth-wallet-button"]';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]:visible';
|
||||
const connectToEthButton =
|
||||
'[data-testid="connect-to-eth-wallet-button"]:visible';
|
||||
const capsuleWalletConnectButton = '[data-testid="web3-connector-Unknown"]';
|
||||
|
||||
Cypress.Commands.add('ethereum_wallet_connect', () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import { ethers, Wallet } from 'ethers';
|
||||
|
||||
const vegaWalletContainer = '[data-testid="vega-wallet"]';
|
||||
const vegaWalletContainer = 'aside [data-testid="vega-wallet"]';
|
||||
const vegaWalletMnemonic = Cypress.env('vegaWalletMnemonic');
|
||||
const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey');
|
||||
const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
|
||||
@@ -95,7 +95,7 @@ Cypress.Commands.add('faucet_asset', function (assetEthAddress) {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('vega_wallet_teardown', function () {
|
||||
cy.get('[data-testid="associated-amount"]')
|
||||
cy.get('aside [data-testid="associated-amount"]')
|
||||
.should('be.visible')
|
||||
.invoke('text')
|
||||
.as('associatedAmount');
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { EthWallet } from '../eth-wallet';
|
||||
import { VegaWallet } from '../vega-wallet';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Route {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
const DrawerSection = ({ children }: { children: React.ReactNode }) => (
|
||||
<div className="px-4 my-4">{children}</div>
|
||||
);
|
||||
|
||||
const IconLine = ({ inverted }: { inverted: boolean }) => (
|
||||
<span className={`block w-6 h-[2px] ${inverted ? 'bg-black' : 'bg-white'}`} />
|
||||
);
|
||||
|
||||
const DrawerNavLinks = ({
|
||||
isInverted,
|
||||
routes,
|
||||
}: {
|
||||
isInverted?: boolean;
|
||||
routes: Route[];
|
||||
}) => {
|
||||
const { appDispatch } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
const linkProps = {
|
||||
end: true,
|
||||
onClick: () =>
|
||||
appDispatch({ type: AppStateActionType.SET_DRAWER, isOpen: false }),
|
||||
};
|
||||
const navClasses = classNames('flex flex-col');
|
||||
|
||||
return (
|
||||
<nav className={navClasses}>
|
||||
{routes.map(({ name, path }) => {
|
||||
return (
|
||||
<NavLink
|
||||
{...linkProps}
|
||||
to={{ pathname: path }}
|
||||
className={({ isActive }) =>
|
||||
classNames({
|
||||
'bg-vega-yellow text-black': !isInverted && isActive,
|
||||
'bg-transparent text-white hover:text-vega-yellow':
|
||||
!isInverted && !isActive,
|
||||
'bg-black text-white': isInverted && isActive,
|
||||
'bg-transparent text-black hover:text-white':
|
||||
isInverted && !isActive,
|
||||
'border-t border-white p-4': true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t(name)}
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export const NavDrawer = ({
|
||||
inverted,
|
||||
routes,
|
||||
}: {
|
||||
inverted: boolean;
|
||||
routes: Route[];
|
||||
}) => {
|
||||
const { appState, appDispatch } = useAppState();
|
||||
|
||||
const drawerContentClasses = classNames(
|
||||
'drawer-content', // needed for css animation
|
||||
// Positions the modal in the center of screen
|
||||
'fixed w-[80vw] max-w-[420px] top-0 right-0',
|
||||
'flex flex-col flex-nowrap justify-between h-full bg-banner overflow-y-scroll border-l border-white',
|
||||
'bg-black text-neutral-200'
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_DRAWER,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
className="flex flex-col flex-nowrap gap-1"
|
||||
>
|
||||
<IconLine inverted={inverted} />
|
||||
<IconLine inverted={inverted} />
|
||||
<IconLine inverted={inverted} />
|
||||
</button>
|
||||
|
||||
<Dialog.Root
|
||||
open={appState.drawerOpen}
|
||||
onOpenChange={(isOpen) =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_DRAWER,
|
||||
isOpen,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-white/15" />
|
||||
<Dialog.Content className={drawerContentClasses}>
|
||||
<div>
|
||||
<DrawerSection>
|
||||
<EthWallet />
|
||||
</DrawerSection>
|
||||
<DrawerSection>
|
||||
<VegaWallet />
|
||||
</DrawerSection>
|
||||
</div>
|
||||
<DrawerNavLinks routes={routes} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import Routes, { TOKEN_DROPDOWN_ROUTES } from '../../routes/routes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { NavbarTheme } from './nav-link';
|
||||
import { AppNavLink } from './nav-link';
|
||||
import {
|
||||
NavDropdownMenu,
|
||||
NavDropdownMenuContent,
|
||||
NavDropdownMenuItem,
|
||||
NavDropdownMenuTrigger,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const NavDropDown = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
|
||||
const { t } = useTranslation();
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
return (
|
||||
<NavDropdownMenu open={isOpen} onOpenChange={(open) => setOpen(open)}>
|
||||
<AppNavLink
|
||||
name={
|
||||
<NavDropdownMenuTrigger
|
||||
className="w-auto flex items-center -m-3 p-3 cursor-pointer"
|
||||
data-testid="state-trigger"
|
||||
onClick={() => setOpen(!isOpen)}
|
||||
>
|
||||
{t('Token')}
|
||||
</NavDropdownMenuTrigger>
|
||||
}
|
||||
testId="token-dd"
|
||||
path={Routes.TOKEN}
|
||||
navbarTheme={navbarTheme}
|
||||
/>
|
||||
|
||||
<NavDropdownMenuContent data-testid="token-dropdown">
|
||||
{TOKEN_DROPDOWN_ROUTES.map((r) => (
|
||||
<NavDropdownMenuItem key={r.name} onClick={() => setOpen(false)}>
|
||||
<AppNavLink
|
||||
testId={r.name}
|
||||
name={t(r.name)}
|
||||
path={r.path}
|
||||
navbarTheme={'inherit'}
|
||||
subNav={true}
|
||||
end={true}
|
||||
fullWidth={true}
|
||||
/>
|
||||
</NavDropdownMenuItem>
|
||||
))}
|
||||
</NavDropdownMenuContent>
|
||||
</NavDropdownMenu>
|
||||
);
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import type { HTMLAttributeAnchorTarget, ReactNode } from 'react';
|
||||
import { getNavLinkClassNames } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export type NavbarTheme = 'inherit' | 'dark' | 'yellow';
|
||||
|
||||
interface AppNavLinkProps {
|
||||
name: ReactNode | string;
|
||||
path: string;
|
||||
navbarTheme: NavbarTheme;
|
||||
testId?: string;
|
||||
target?: HTMLAttributeAnchorTarget;
|
||||
end?: boolean;
|
||||
fullWidth?: boolean;
|
||||
subNav?: boolean;
|
||||
}
|
||||
|
||||
export const AppNavLink = ({
|
||||
name,
|
||||
path,
|
||||
navbarTheme,
|
||||
target,
|
||||
testId,
|
||||
end = false,
|
||||
fullWidth = false,
|
||||
subNav = false,
|
||||
}: AppNavLinkProps) => {
|
||||
const borderClasses = classNames(
|
||||
'absolute h-0.5 w-full bottom-[-1px] left-0',
|
||||
{
|
||||
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
|
||||
'bg-black': navbarTheme === 'yellow',
|
||||
}
|
||||
);
|
||||
return (
|
||||
<NavLink
|
||||
key={path}
|
||||
data-testid={testId}
|
||||
to={{ pathname: path }}
|
||||
className={getNavLinkClassNames(navbarTheme, fullWidth, subNav)}
|
||||
target={target}
|
||||
end={end}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
return (
|
||||
<div className={subNav ? 'inline-block relative pb-1' : undefined}>
|
||||
{name}
|
||||
{isActive && (
|
||||
<span data-testid="link-active" className={borderClasses} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-content[data-state='open'] {
|
||||
animation: slideIn 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
.drawer-content[data-state='closed'] {
|
||||
animation: slideOut 150ms ease-in forwards;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Nav } from './nav';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
NetworkSwitcher: () => <div data-testid="network-switcher" />,
|
||||
useEnvironment: () => ({ VEGA_ENV: 'MAINNET' }),
|
||||
}));
|
||||
|
||||
const renderComponent = (initialEntries?: string[]) => {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={initialEntries}>
|
||||
<Nav />
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
describe('nav', () => {
|
||||
it('Renders logo with link to home', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('logo-link')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/'
|
||||
);
|
||||
});
|
||||
it('Renders network switcher', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('network-switcher')).toBeInTheDocument();
|
||||
});
|
||||
it('Renders all top level routes', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('Proposals')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/proposals'
|
||||
);
|
||||
expect(screen.getByTestId('Validators')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/validators'
|
||||
);
|
||||
expect(screen.getByTestId('Rewards')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/rewards'
|
||||
);
|
||||
});
|
||||
it('Shows active state on dropdown trigger when on home route for subroutes', () => {
|
||||
const { getByTestId } = renderComponent(['/token']);
|
||||
const dd = getByTestId('token-dd');
|
||||
expect(within(dd).getByTestId('link-active')).toBeInTheDocument();
|
||||
});
|
||||
it('Shows active state on dropdown trigger when on sub route of dropdown', () => {
|
||||
const { getByTestId } = renderComponent(['/token/withdraw']);
|
||||
const dd = getByTestId('token-dd');
|
||||
expect(within(dd).getByTestId('link-active')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,81 +1,85 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TOP_LEVEL_ROUTES } from '../../routes/routes';
|
||||
import { TOKEN_DROPDOWN_ROUTES, TOP_LEVEL_ROUTES } from '../../routes/routes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import logoWhiteText from '../../images/logo-white-text.png';
|
||||
import logoBlackText from '../../images/logo-black-text.png';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { NavDrawer } from './nav-draw';
|
||||
import { Nav as ToolkitNav } from '@vegaprotocol/ui-toolkit';
|
||||
import { AppNavLink } from './nav-link';
|
||||
import { NavDropDown } from './nav-dropdown';
|
||||
|
||||
const useDebouncedResize = () => {
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResizeDebounced = debounce(() => {
|
||||
setWindowWidth(window.innerWidth);
|
||||
}, 300);
|
||||
|
||||
window.addEventListener('resize', handleResizeDebounced);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResizeDebounced);
|
||||
};
|
||||
}, []);
|
||||
return {
|
||||
windowWidth,
|
||||
};
|
||||
};
|
||||
|
||||
type NavbarTheme = 'inherit' | 'dark' | 'yellow';
|
||||
interface NavbarProps {
|
||||
navbarTheme?: NavbarTheme;
|
||||
}
|
||||
|
||||
export const Nav = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
||||
const { windowWidth } = useDebouncedResize();
|
||||
const isDesktop = windowWidth > 995;
|
||||
import type { NavigationProps } from '@vegaprotocol/ui-toolkit';
|
||||
import { useNavigationDrawer } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Navigation,
|
||||
NavigationBreakpoint,
|
||||
NavigationContent,
|
||||
NavigationItem,
|
||||
NavigationLink,
|
||||
NavigationList,
|
||||
NavigationTrigger,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { EthWallet } from '../eth-wallet';
|
||||
import { VegaWallet } from '../vega-wallet';
|
||||
import { useLocation, useMatch } from 'react-router-dom';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Nav = ({ theme }: Pick<NavigationProps, 'theme'>) => {
|
||||
const { t } = useTranslation();
|
||||
const isYellow = navbarTheme === 'yellow';
|
||||
const setDrawerOpen = useNavigationDrawer((state) => state.setDrawerOpen);
|
||||
|
||||
const location = useLocation();
|
||||
const isOnToken = useMatch('token/*');
|
||||
useEffect(() => {
|
||||
setDrawerOpen(false);
|
||||
}, [location, setDrawerOpen]);
|
||||
|
||||
const topLevel = TOP_LEVEL_ROUTES.map(({ name, path }) => (
|
||||
<NavigationItem key={name}>
|
||||
<NavigationLink to={path}>{name}</NavigationLink>
|
||||
</NavigationItem>
|
||||
));
|
||||
|
||||
const secondLevel = TOKEN_DROPDOWN_ROUTES.map(({ name, path, end }) => (
|
||||
<NavigationItem key={name}>
|
||||
<NavigationLink to={path} end={Boolean(end)}>
|
||||
{name}
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
));
|
||||
|
||||
return (
|
||||
<ToolkitNav
|
||||
navbarTheme={navbarTheme}
|
||||
icon={
|
||||
<Link to="/" data-testid="logo-link">
|
||||
<img
|
||||
alt="Vega"
|
||||
src={navbarTheme === 'yellow' ? logoBlackText : logoWhiteText}
|
||||
height={30}
|
||||
width={250}
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
title={undefined}
|
||||
titleContent={<NetworkSwitcher />}
|
||||
>
|
||||
{isDesktop ? (
|
||||
<nav className="flex items-center flex-1 px-4">
|
||||
{TOP_LEVEL_ROUTES.map((r) => (
|
||||
<AppNavLink
|
||||
key={r.path}
|
||||
testId={r.name}
|
||||
name={t(r.name)}
|
||||
path={r.path}
|
||||
navbarTheme={navbarTheme}
|
||||
/>
|
||||
))}
|
||||
<NavDropDown navbarTheme={navbarTheme} />
|
||||
</nav>
|
||||
) : (
|
||||
<nav className="flex items-center flex-1 px-2 justify-end">
|
||||
<NavDrawer inverted={isYellow} routes={TOP_LEVEL_ROUTES} />
|
||||
</nav>
|
||||
)}
|
||||
</ToolkitNav>
|
||||
<Navigation appName="Governance" theme={theme} breakpoints={[458, 959]}>
|
||||
<NavigationList
|
||||
className="[.drawer-content_&]:border-b [.drawer-content_&]:border-b-vega-light-200 dark:[.drawer-content_&]:border-b-vega-dark-200 [.drawer-content_&]:pb-8 [.drawer-content_&]:mb-2"
|
||||
hide={[NavigationBreakpoint.Small]}
|
||||
>
|
||||
<NavigationItem className="[.drawer-content_&]:w-full">
|
||||
<NetworkSwitcher className="[.drawer-content_&]:w-full" />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Narrow, NavigationBreakpoint.Small]}
|
||||
>
|
||||
{topLevel}
|
||||
<NavigationItem>
|
||||
<NavigationTrigger
|
||||
data-testid="state-trigger"
|
||||
isActive={Boolean(isOnToken)}
|
||||
>
|
||||
{t('Token')}
|
||||
</NavigationTrigger>
|
||||
<NavigationContent>
|
||||
<NavigationList data-testid="token-dropdown">
|
||||
{secondLevel}
|
||||
</NavigationList>
|
||||
</NavigationContent>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={true}
|
||||
className="[.drawer-content_&]:border-t [.drawer-content_&]:border-t-vega-light-200 dark:[.drawer-content_&]:border-t-vega-dark-200 [.drawer-content_&]:pt-8 [.drawer-content_&]:mt-4"
|
||||
>
|
||||
<NavigationItem className="[.drawer-content_&]:w-full">
|
||||
<EthWallet />
|
||||
</NavigationItem>
|
||||
<NavigationItem className="[.drawer-content_&]:w-full">
|
||||
<VegaWallet />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
</Navigation>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,13 +21,13 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
|
||||
<>
|
||||
<AnnouncementBanner>
|
||||
<div className="font-alpha calt uppercase text-center text-lg text-white">
|
||||
<span className="pr-4">Mainnet sim 2 is live!</span>
|
||||
<span className="pr-4">Mainnet sim 3 is coming soon!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">
|
||||
Come help stress test the network
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
<Nav navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
|
||||
<Nav theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
|
||||
{isReadOnly ? (
|
||||
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
|
||||
) : null}
|
||||
|
||||
@@ -37,9 +37,6 @@ export interface AppState {
|
||||
/** Whether or not the connect to Ethereum wallet overlay is open */
|
||||
ethConnectOverlay: boolean;
|
||||
|
||||
/** Whether or not the mobile drawer is open. Only relevant on screens smaller than 960 */
|
||||
drawerOpen: boolean;
|
||||
|
||||
/** Whether or not the transaction modal is open */
|
||||
transactionOverlay: boolean;
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,6 @@ const initialAppState: AppState = {
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
drawerOpen: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
};
|
||||
@@ -36,7 +35,6 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
|
||||
return {
|
||||
...state,
|
||||
vegaWalletOverlay: action.isOpen,
|
||||
drawerOpen: action.isOpen ? false : state.drawerOpen,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_VEGA_WALLET_MANAGE_OVERLAY: {
|
||||
@@ -44,20 +42,17 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
|
||||
...state,
|
||||
vegaWalletManageOverlay: action.isOpen,
|
||||
vegaWalletOverlay: action.isOpen ? false : state.vegaWalletOverlay,
|
||||
drawerOpen: action.isOpen ? false : state.drawerOpen,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_ETH_WALLET_OVERLAY: {
|
||||
return {
|
||||
...state,
|
||||
ethConnectOverlay: action.isOpen,
|
||||
drawerOpen: action.isOpen ? false : state.drawerOpen,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_DRAWER: {
|
||||
return {
|
||||
...state,
|
||||
drawerOpen: action.isOpen,
|
||||
vegaWalletOverlay: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,26 +9,28 @@ export type PendingTxsStore = {
|
||||
resetPendingTxs: () => void;
|
||||
};
|
||||
|
||||
export const usePendingBalancesStore = create<PendingTxsStore>((set, get) => ({
|
||||
pendingBalances: [],
|
||||
addPendingTxs: (event: Event[]) => {
|
||||
set({
|
||||
pendingBalances: uniqBy(
|
||||
[...get().pendingBalances, ...event],
|
||||
'transactionHash'
|
||||
),
|
||||
});
|
||||
},
|
||||
removePendingTx: (event: Event) => {
|
||||
set({
|
||||
pendingBalances: [
|
||||
...get().pendingBalances.filter(
|
||||
({ transactionHash }) => transactionHash !== event.transactionHash
|
||||
export const usePendingBalancesStore = create<PendingTxsStore>()(
|
||||
(set, get) => ({
|
||||
pendingBalances: [],
|
||||
addPendingTxs: (event: Event[]) => {
|
||||
set({
|
||||
pendingBalances: uniqBy(
|
||||
[...get().pendingBalances, ...event],
|
||||
'transactionHash'
|
||||
),
|
||||
],
|
||||
});
|
||||
},
|
||||
resetPendingTxs: () => {
|
||||
set({ pendingBalances: [] });
|
||||
},
|
||||
}));
|
||||
});
|
||||
},
|
||||
removePendingTx: (event: Event) => {
|
||||
set({
|
||||
pendingBalances: [
|
||||
...get().pendingBalances.filter(
|
||||
({ transactionHash }) => transactionHash !== event.transactionHash
|
||||
),
|
||||
],
|
||||
});
|
||||
},
|
||||
resetPendingTxs: () => {
|
||||
set({ pendingBalances: [] });
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface RefreshBalances {
|
||||
vestingAssociatedBalance: BigNumber;
|
||||
}
|
||||
|
||||
export const useBalances = create<BalancesStore>((set) => ({
|
||||
export const useBalances = create<BalancesStore>()((set) => ({
|
||||
associationBreakdown: {
|
||||
stakingAssociations: {},
|
||||
vestingAssociations: {},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import type { TrancheServiceResponse } from '@vegaprotocol/smart-contracts';
|
||||
import type BigNumber from 'bignumber.js';
|
||||
import create from 'zustand';
|
||||
import { create } from 'zustand';
|
||||
import { ENV } from '../../config';
|
||||
|
||||
export interface Tranche {
|
||||
@@ -36,7 +36,7 @@ export type TranchesStore = {
|
||||
|
||||
const secondsToDate = (seconds: number) => new Date(seconds * 1000);
|
||||
|
||||
export const useTranches = create<TranchesStore>((set) => ({
|
||||
export const useTranches = create<TranchesStore>()((set) => ({
|
||||
tranches: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
@@ -54,7 +54,6 @@ const mockAppState: AppState = {
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
drawerOpen: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ export const VoteDetails = ({
|
||||
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
|
||||
<section>
|
||||
<SubHeading title={t('liquidityVotes')} />
|
||||
<p>
|
||||
<p data-testid="liquidity-votes-status">
|
||||
<span>
|
||||
<CurrentProposalStatus proposal={proposal} />
|
||||
</span>
|
||||
@@ -105,7 +105,7 @@ export const VoteDetails = ({
|
||||
)}
|
||||
<section data-testid="votes-table">
|
||||
<SubHeading title={t('tokenVotes')} />
|
||||
<p>
|
||||
<p data-testid="token-votes-status">
|
||||
<span>
|
||||
<CurrentProposalStatus proposal={proposal} />
|
||||
</span>
|
||||
|
||||
@@ -17,7 +17,6 @@ const mockAppState: AppState = {
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
drawerOpen: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
};
|
||||
|
||||
@@ -84,6 +84,7 @@ const RedemptionRouter = () => {
|
||||
fill={true}
|
||||
variant="primary"
|
||||
onClick={() => navigate(`${RoutesConfig.REDEEM}/${account}`)}
|
||||
data-testid="view-connected-eth-btn"
|
||||
>
|
||||
{t('View connected Eth Wallet')}
|
||||
</Button>
|
||||
|
||||
@@ -37,6 +37,7 @@ export const TOKEN_DROPDOWN_ROUTES = [
|
||||
{
|
||||
name: 'Token',
|
||||
path: Routes.TOKEN,
|
||||
end: true,
|
||||
},
|
||||
{
|
||||
name: 'Supply & Vesting',
|
||||
|
||||
@@ -73,6 +73,7 @@ export const Tranche = () => {
|
||||
className="underline"
|
||||
title={t('View vesting information')}
|
||||
to={`${Routes.REDEEM}/${user}`}
|
||||
data-testid="redeem-link"
|
||||
>
|
||||
{t('View vesting information')}
|
||||
</RouterLink>
|
||||
|
||||
@@ -50,7 +50,10 @@ export const Tranches = () => {
|
||||
)}
|
||||
|
||||
<section className="text-center mt-4">
|
||||
<ButtonLink onClick={() => setShowAll(!showAll)}>
|
||||
<ButtonLink
|
||||
data-testid="show-all-tranches"
|
||||
onClick={() => setShowAll(!showAll)}
|
||||
>
|
||||
{showAll
|
||||
? t(
|
||||
'Showing tranches with <{{trancheMinimum}} VEGA, click to hide these tranches',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type ethers from 'ethers';
|
||||
import type { GetState, SetState } from 'zustand';
|
||||
import { create } from 'zustand';
|
||||
|
||||
export interface TxData {
|
||||
@@ -16,27 +15,25 @@ interface TransactionStore {
|
||||
remove: (tx: TxData) => void;
|
||||
}
|
||||
|
||||
export const useTransactionStore = create(
|
||||
(set: SetState<TransactionStore>, get: GetState<TransactionStore>) => ({
|
||||
transactions: [],
|
||||
add: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({ transactions: [...transactions, tx] });
|
||||
},
|
||||
update: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({
|
||||
transactions: [
|
||||
...transactions.filter((t) => t.tx.hash !== tx.tx.hash),
|
||||
tx,
|
||||
],
|
||||
});
|
||||
},
|
||||
remove: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({
|
||||
transactions: transactions.filter((t) => t.tx.hash !== tx.tx.hash),
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
export const useTransactionStore = create<TransactionStore>()((set, get) => ({
|
||||
transactions: [],
|
||||
add: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({ transactions: [...transactions, tx] });
|
||||
},
|
||||
update: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({
|
||||
transactions: [
|
||||
...transactions.filter((t) => t.tx.hash !== tx.tx.hash),
|
||||
tx,
|
||||
],
|
||||
});
|
||||
},
|
||||
remove: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({
|
||||
transactions: transactions.filter((t) => t.tx.hash !== tx.tx.hash),
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useMemo } from 'react';
|
||||
import { makeDerivedDataProvider } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
@@ -43,7 +42,7 @@ const useMarketDetails = (marketId: string | undefined) => {
|
||||
const { data, loading, error } = useDataProvider({
|
||||
dataProvider: lpDataProvider,
|
||||
skipUpdates: true,
|
||||
variables: useMemo(() => ({ marketId }), [marketId]),
|
||||
variables: { marketId: marketId || '' },
|
||||
});
|
||||
|
||||
const liquidityProviders = data?.liquidityProviders || [];
|
||||
|
||||
+7
-10
@@ -39,14 +39,11 @@ export const Last24hVolume = ({
|
||||
[marketId, yTimestamp]
|
||||
);
|
||||
|
||||
const variables24hAgo = useMemo(
|
||||
() => ({
|
||||
marketId: marketId,
|
||||
interval: Schema.Interval.INTERVAL_I1D,
|
||||
since: yTimestamp,
|
||||
}),
|
||||
[marketId, yTimestamp]
|
||||
);
|
||||
const variables24hAgo = {
|
||||
marketId: marketId,
|
||||
interval: Schema.Interval.INTERVAL_I1D,
|
||||
since: yTimestamp,
|
||||
};
|
||||
|
||||
const throttledSetCandles = useRef(
|
||||
throttle((data: Candle[]) => {
|
||||
@@ -64,7 +61,7 @@ export const Last24hVolume = ({
|
||||
[throttledSetCandles]
|
||||
);
|
||||
|
||||
const { data, error } = useDataProvider<Candle[], Candle>({
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: marketCandlesProvider,
|
||||
variables: variables,
|
||||
update,
|
||||
@@ -88,7 +85,7 @@ export const Last24hVolume = ({
|
||||
[throttledSetVolumeChange]
|
||||
);
|
||||
|
||||
useDataProvider<Candle[], Candle>({
|
||||
useDataProvider({
|
||||
dataProvider: marketCandlesProvider,
|
||||
update: updateCandle24hAgo,
|
||||
variables: variables24hAgo,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user