Compare commits
62
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64bf2b3b52 | ||
|
|
28f9e169e7 | ||
|
|
162b167860 | ||
|
|
a4149cc55e | ||
|
|
4161a74eaf | ||
|
|
5008e57a56 | ||
|
|
89031bf826 | ||
|
|
61501d4d14 | ||
|
|
f8f3ce7df9 | ||
|
|
f7aab5a78a | ||
|
|
2904975fd3 | ||
|
|
4f55c946f0 | ||
|
|
de637fee13 | ||
|
|
69c6db8ec2 | ||
|
|
8dc465c1d1 | ||
|
|
0be934edca | ||
|
|
653688c1dd | ||
|
|
34400be18a | ||
|
|
d4a0a4a42c | ||
|
|
fabf643ce7 | ||
|
|
45e210092c | ||
|
|
1dc3b3fd05 | ||
|
|
0a80e3e39d | ||
|
|
7b8d9dc94b | ||
|
|
c0320c2468 | ||
|
|
11173d6614 | ||
|
|
e3324331e2 | ||
|
|
0a6e3cb6f5 | ||
|
|
68ede90609 | ||
|
|
09778a5d3f | ||
|
|
59993e3ea6 | ||
|
|
54161a6dba | ||
|
|
26f524ea07 | ||
|
|
db13e6f21d | ||
|
|
4a679813ba | ||
|
|
a4279d5b5e | ||
|
|
1a274a67c3 | ||
|
|
1e9ff31fd7 | ||
|
|
aa2b755b93 | ||
|
|
4a7296312a | ||
|
|
1418a2259f | ||
|
|
7e5ef0157a | ||
|
|
0cf764f11e | ||
|
|
1c626aeeb2 | ||
|
|
c4cec47b0b | ||
|
|
1a67c41bb4 | ||
|
|
fc66240390 | ||
|
|
fc0da98683 | ||
|
|
4d0dafb113 | ||
|
|
eda431be9e | ||
|
|
4ff1749e54 | ||
|
|
e7a108fd7a | ||
|
|
5d3f2ea7d8 | ||
|
|
1ab27574ca | ||
|
|
0ea3c43e78 | ||
|
|
9e183568b0 | ||
|
|
69fd5b26b6 | ||
|
|
2d5b926609 | ||
|
|
98a2ba79a8 | ||
|
|
0e6e986937 | ||
|
|
15d6ea0c05 | ||
|
|
0358ce6293 |
@@ -0,0 +1,74 @@
|
||||
name: After Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
after-release:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to the Container registry (ghcr)
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log in to the Container registry (docker hub)
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: resolve ipfs hashes for release
|
||||
run: |
|
||||
echo "Tag name: ${{ github.event.release.tag_name }}"
|
||||
echo "Name: ${{ github.event.release.name }}"
|
||||
echo "Description: ${{ github.event.release.body }}"
|
||||
until docker pull vegaprotocol/trading:${{ github.event.release.tag_name }}; do
|
||||
echo "Image not pushed yet, waiting 60 seconds"
|
||||
sleep 60
|
||||
done
|
||||
docker run --rm vegaprotocol/trading:${{ github.event.release.tag_name }} cat /ipfs-hash > ipfs-hash
|
||||
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
|
||||
tar -xzf kubo.tgz
|
||||
export PATH="$PATH:$PWD/kubo"
|
||||
which ipfs
|
||||
echo IPFS_V0=$(cat ipfs-hash) >> $GITHUB_ENV
|
||||
echo IPFS_V1=$(ipfs cid format -v 1 -b base32 $(cat ipfs-hash)) >> $GITHUB_ENV
|
||||
|
||||
- name: Edit Release
|
||||
uses: irongut/EditRelease@v1.2.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
id: ${{ github.event.release.id }}
|
||||
body: |
|
||||
---
|
||||
|
||||
# Deployments
|
||||
* https://explorer.vega.xyz
|
||||
* https://governance.vega.xyz
|
||||
|
||||
# IPFS releases
|
||||
Tye IPFS hash of this release of the Trading app is:
|
||||
|
||||
CIDv0: ${{ env.IPFS_V0 }}
|
||||
CIDv1: ${{ env.IPFS_V1 }}
|
||||
|
||||
You can always access the latest IPFS release by visiting [vega.trading](https://vega.trading).
|
||||
|
||||
You can also access Trading directly from an IPFS gateway.
|
||||
BEWARE: The Trading interface uses [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported. You should always use an IPFS gateway that enforces [origin separation](https://ipfs.github.io/public-gateway-checker/).
|
||||
|
||||
Your settings are not remembered across different URLs.
|
||||
|
||||
IPFS gateways:
|
||||
|
||||
https://${{ env.IPFS_V1 }}.ipfs.dweb.link/
|
||||
https://${{ env.IPFS_V1 }}.ipfs.cf-ipfs.com/
|
||||
ipfs://${{ env.IPFS_V0 }}/
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
project: ${{ fromJSON(inputs.projects) }}
|
||||
name: ${{ matrix.project }}
|
||||
runs-on: self-hosted-runner
|
||||
timeout-minutes: 40
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
# Checks if skip cache was requested
|
||||
- name: Set skip-nx-cache flag
|
||||
@@ -98,4 +98,4 @@ jobs:
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: test-report-${{ matrix.project }}
|
||||
path: frontend-monorepo/apps/trading-e2e/cypress/reports
|
||||
path: frontend-monorepo/apps/${{ matrix.project }}/cypress/reports
|
||||
|
||||
@@ -170,12 +170,6 @@ jobs:
|
||||
labels: ${{ matrix.app }}-preview
|
||||
number: ${{ github.event.number }}
|
||||
|
||||
- name: Add ipfs hash to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
|
||||
with:
|
||||
files: ${{ matrix.app }}-ipfs-hash
|
||||
|
||||
- name: Trigger fleek deployment
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
@@ -191,28 +185,36 @@ jobs:
|
||||
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
|
||||
https://api.fleek.co/graphql
|
||||
|
||||
- name: Checkout vega.xyz
|
||||
- name: Update vega.trading DNS to redirect to the new console
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: vegaprotocol/vega.xyz
|
||||
path: './vega-xyz'
|
||||
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
|
||||
|
||||
- name: Update hash on interstitial page
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
|
||||
run: |
|
||||
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
|
||||
tar -xzf kubo.tgz
|
||||
export PATH="$PATH:$PWD/kubo"
|
||||
which ipfs
|
||||
new_hash=$(cat ${{ matrix.app }}-ipfs-hash)
|
||||
cd vega-xyz
|
||||
./interstital-allow-update.sh ${new_hash}
|
||||
git config --global user.email "vega-ci-bot@vega.xyz"
|
||||
git config --global user.name "vega-ci-bot"
|
||||
git add interstitial-allow.json netlify.toml
|
||||
git commit -m "feat(ci): Update CID for console release @ ${{ github.ref }}"
|
||||
git push origin main
|
||||
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
|
||||
|
||||
# Generate console URL
|
||||
new_console_url_type=ipfs
|
||||
# new_console_url_type=ipns
|
||||
new_console_url_domain=cf-ipfs.com
|
||||
# new_console_url_domain=dweb.link
|
||||
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
|
||||
echo "new_console_url=${new_console_url}"
|
||||
|
||||
# Update record in DNSimple
|
||||
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
|
||||
dnsimple_account_id=84895
|
||||
dnsimple_zone_name=vega.trading
|
||||
dnsimple_record_id=44409591
|
||||
# see: https://dnsimple.com/a/84895/domains/vega.trading/records/44409591/edit
|
||||
|
||||
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-X PATCH \
|
||||
-d "{
|
||||
\"content\": \"${new_console_url}\"
|
||||
}" \
|
||||
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
name: 'Rollback console'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version that should be set on rollback'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
rollback:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to the Container registry (docker hub)
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Retag mainnet
|
||||
run: |
|
||||
docker pull vegaprotocol/trading:${{ inputs.version }}
|
||||
docker tag vegaprotocol/trading:${{ inputs.version }} vegaprotocol/trading:mainnet
|
||||
docker push vegaprotocol/trading:mainnet
|
||||
docker run --rm vegaprotocol/trading:mainnet cat /ipfs-hash > ipfs-hash
|
||||
|
||||
- name: Trigger fleek deployment
|
||||
run: |
|
||||
# display info about app
|
||||
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \
|
||||
https://api.fleek.co/graphql
|
||||
|
||||
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
|
||||
curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
|
||||
https://api.fleek.co/graphql
|
||||
|
||||
- name: Update vega.trading DNS to redirect to the new console
|
||||
run: |
|
||||
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
|
||||
tar -xzf kubo.tgz
|
||||
export PATH="$PATH:$PWD/kubo"
|
||||
which ipfs
|
||||
new_hash=$(cat ipfs-hash)
|
||||
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
|
||||
|
||||
# Generate console URL
|
||||
new_console_url_type=ipfs
|
||||
# new_console_url_type=ipns
|
||||
new_console_url_domain=cf-ipfs.com
|
||||
# new_console_url_domain=dweb.link
|
||||
new_console_url="https://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
|
||||
echo "new_console_url=${new_console_url}"
|
||||
|
||||
# Update record in DNSimple
|
||||
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
|
||||
dnsimple_account_id=84895
|
||||
dnsimple_zone_name=vega.trading
|
||||
dnsimple_record_id=44409591
|
||||
# see: https://dnsimple.com/a/84895/domains/vega.trading/records/44409591/edit
|
||||
|
||||
curl -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-X PATCH \
|
||||
-d "{
|
||||
\"content\": \"${new_console_url}\"
|
||||
}" \
|
||||
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
|
||||
+1
-1
@@ -49,4 +49,4 @@ cypress.env.json
|
||||
.next
|
||||
|
||||
#cypress
|
||||
/apps/trading-e2e/cypress/reports/
|
||||
/apps/**/cypress/reports/
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
const { defineConfig } = require('cypress');
|
||||
|
||||
module.exports = defineConfig({
|
||||
projectId: 'et4snf',
|
||||
reporter: '../../node_modules/cypress-mochawesome-reporter',
|
||||
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
require('cypress-mochawesome-reporter/plugin')(on);
|
||||
require('@cypress/grep/src/plugin')(config);
|
||||
return config;
|
||||
},
|
||||
|
||||
@@ -15,5 +15,6 @@
|
||||
|
||||
import '@vegaprotocol/cypress';
|
||||
import './common.functions.js';
|
||||
import 'cypress-mochawesome-reporter/register';
|
||||
import registerCypressGrep from '@cypress/grep';
|
||||
registerCypressGrep();
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Header } from './header';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
NetworkSwitcher: () => (
|
||||
<div data-testid="network-switcher">NetworkSwitcher</div>
|
||||
),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { MarketInfoWithData } from '@vegaprotocol/market-info';
|
||||
import { LiquidityInfoPanel } from '@vegaprotocol/market-info';
|
||||
import { LiquidityMonitoringParametersInfoPanel } from '@vegaprotocol/market-info';
|
||||
import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
||||
import {
|
||||
LiquidityInfoPanel,
|
||||
LiquidityMonitoringParametersInfoPanel,
|
||||
InstrumentInfoPanel,
|
||||
KeyDetailsInfoPanel,
|
||||
LiquidityPriceRangeInfoPanel,
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
RiskModelInfoPanel,
|
||||
RiskParametersInfoPanel,
|
||||
SettlementAssetInfoPanel,
|
||||
} from '@vegaprotocol/market-info';
|
||||
import { MarketInfoTable } from '@vegaprotocol/market-info';
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketInfoTable } from '@vegaprotocol/markets';
|
||||
import type { DataSourceDefinition } from '@vegaprotocol/types';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MarketFieldsFragment } from '@vegaprotocol/market-list';
|
||||
import type { MarketFieldsFragment } from '@vegaprotocol/markets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
|
||||
@@ -8,7 +8,7 @@ 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 { marketInfoWithDataProvider } from '@vegaprotocol/market-info';
|
||||
import { marketInfoWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { PageTitle } from '../../components/page-helpers/page-title';
|
||||
|
||||
export const MarketPage = () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useScrollToLocation } from '../../hooks/scroll-to-location';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import { marketsProvider } from '@vegaprotocol/market-list';
|
||||
import { marketsProvider } from '@vegaprotocol/markets';
|
||||
import { RouteTitle } from '../../components/route-title';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
|
||||
}
|
||||
|
||||
.vega-ag-grid .ag-row {
|
||||
border-width: 1px 0;
|
||||
border-bottom: 1px solid transparent;
|
||||
.vega-ag-grid .ag-row,
|
||||
.vega-ag-grid .ag-cell {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* Light variables */
|
||||
@@ -46,7 +46,6 @@
|
||||
--ag-header-column-separator-color: theme(colors.neutral[300]);
|
||||
--ag-row-border-color: theme(colors.white);
|
||||
--ag-row-hover-color: theme(colors.neutral[100]);
|
||||
--ag-font-size: 12px;
|
||||
}
|
||||
|
||||
/* Dark variables */
|
||||
@@ -58,7 +57,6 @@
|
||||
--ag-header-column-separator-color: theme(colors.neutral[600]);
|
||||
--ag-row-border-color: theme(colors.black);
|
||||
--ag-row-hover-color: theme(colors.neutral[800]);
|
||||
--ag-font-size: 12px;
|
||||
}
|
||||
|
||||
.voteicon svg {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
const { defineConfig } = require('cypress');
|
||||
|
||||
module.exports = defineConfig({
|
||||
projectId: 'et4snf',
|
||||
reporter: '../../node_modules/cypress-mochawesome-reporter',
|
||||
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
require('cypress-mochawesome-reporter/plugin')(on);
|
||||
require('@cypress/grep/src/plugin')(config);
|
||||
return config;
|
||||
},
|
||||
|
||||
@@ -63,7 +63,6 @@ context(
|
||||
});
|
||||
|
||||
it('should have option to go to last and newest page', function () {
|
||||
waitForBeginningOfEpoch();
|
||||
cy.getByTestId('goto-last-page').click();
|
||||
cy.getByTestId('epoch-total-rewards-table')
|
||||
.last()
|
||||
|
||||
@@ -8,6 +8,7 @@ import './wallet-eth.functions.ts';
|
||||
import './wallet-teardown.functions.ts';
|
||||
import './wallet-vega.functions.ts';
|
||||
import './proposal.functions.ts';
|
||||
import 'cypress-mochawesome-reporter/register';
|
||||
import registerCypressGrep from '@cypress/grep';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { chainIdQuery, statisticsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
StakingBridge,
|
||||
Token,
|
||||
TokenVesting,
|
||||
TokenFaucetable,
|
||||
CollateralBridge,
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import { ethers, Wallet } from 'ethers';
|
||||
@@ -41,7 +40,7 @@ export async function depositAsset(
|
||||
decimalPlaces: number
|
||||
) {
|
||||
// Approve asset
|
||||
const faucet = new TokenFaucetable(assetEthAddress, signer);
|
||||
const faucet = new Token(assetEthAddress, signer);
|
||||
cy.wrap(
|
||||
faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)),
|
||||
{
|
||||
@@ -62,7 +61,7 @@ export async function depositAsset(
|
||||
}
|
||||
|
||||
export async function faucetAsset(assetEthAddress: string) {
|
||||
const faucet = new TokenFaucetable(assetEthAddress, signer);
|
||||
const faucet = new Token(assetEthAddress, signer);
|
||||
await promiseWithTimeout(faucet.faucet(), 10 * 60 * 1000, 'faucet asset');
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -16,6 +16,7 @@ import { AppStateProvider } from './contexts/app-state/app-state-provider';
|
||||
import { ContractsProvider } from './contexts/contracts/contracts-provider';
|
||||
import { AppRouter } from './routes';
|
||||
import type { EthereumConfig } from '@vegaprotocol/web3';
|
||||
import { WithdrawalApprovalDialogContainer } from '@vegaprotocol/web3';
|
||||
import {
|
||||
createConnectors,
|
||||
useEthTransactionManager,
|
||||
@@ -43,7 +44,7 @@ import {
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ENV } from './config';
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import { WithdrawalDialog } from '@vegaprotocol/withdraws';
|
||||
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
|
||||
import { SplashLoader } from './components/splash-loader';
|
||||
import { ToastsManager } from './toasts-manager';
|
||||
import {
|
||||
@@ -52,6 +53,7 @@ import {
|
||||
} from './components/telemetry-dialog/telemetry-dialog';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { isPartyNotFoundError } from './lib/party';
|
||||
|
||||
const cache: InMemoryCacheConfig = {
|
||||
typePolicies: {
|
||||
@@ -157,7 +159,8 @@ const Web3Container = ({
|
||||
<InitializeHandlers />
|
||||
<VegaWalletDialogs />
|
||||
<TransactionModal />
|
||||
<WithdrawalDialog />
|
||||
<CreateWithdrawalDialog />
|
||||
<WithdrawalApprovalDialogContainer />
|
||||
<TelemetryDialog />
|
||||
</>
|
||||
</BalanceManager>
|
||||
@@ -204,31 +207,43 @@ const AppContainer = () => {
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ENV.dsn && telemetryOn) {
|
||||
if (ENV.dsn && telemetryOn === 'true') {
|
||||
Sentry.init({
|
||||
dsn: ENV.dsn,
|
||||
tracesSampleRate: 0.1,
|
||||
enabled: true,
|
||||
environment: VEGA_ENV,
|
||||
release: GIT_COMMIT_HASH,
|
||||
beforeSend(event) {
|
||||
beforeSend(event, hint) {
|
||||
const error = hint?.originalException;
|
||||
const errorIsString = typeof error === 'string';
|
||||
const errorIsObject = error instanceof Error;
|
||||
const requestUrl = event.request?.url;
|
||||
const transaction = event.transaction;
|
||||
|
||||
if (
|
||||
(errorIsString && isPartyNotFoundError({ message: error })) ||
|
||||
(errorIsObject && isPartyNotFoundError(error))
|
||||
) {
|
||||
// This error is caused by a pubkey making an API request before
|
||||
// it has interacted with the chain. This isn't needed in Sentry.
|
||||
return null;
|
||||
}
|
||||
|
||||
const updatedRequest =
|
||||
requestUrl && requestUrl.includes('/test?')
|
||||
requestUrl && requestUrl.includes('/claim?')
|
||||
? { ...event.request, url: removeQueryParams(requestUrl) }
|
||||
: event.request;
|
||||
|
||||
const updatedTransaction =
|
||||
transaction && transaction.includes('/test?')
|
||||
transaction && transaction.includes('/claim?')
|
||||
? removeQueryParams(transaction)
|
||||
: transaction;
|
||||
|
||||
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
|
||||
if (
|
||||
breadcrumb.type === 'navigation' &&
|
||||
breadcrumb.data?.to?.includes('/test?')
|
||||
breadcrumb.data?.to?.includes('/claim?')
|
||||
) {
|
||||
return {
|
||||
...breadcrumb,
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { useWeb3ConnectStore } from '@vegaprotocol/web3';
|
||||
|
||||
export const EthConnectPrompt = () => {
|
||||
const { t } = useTranslation();
|
||||
const { appDispatch } = useAppState();
|
||||
const { open } = useWeb3ConnectStore();
|
||||
return (
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
onClick={open}
|
||||
fill={true}
|
||||
data-testid="connect-to-eth-btn"
|
||||
>
|
||||
|
||||
@@ -2,10 +2,7 @@ import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { useWeb3ConnectStore } from '@vegaprotocol/web3';
|
||||
|
||||
export const EthWalletContainer = ({
|
||||
children,
|
||||
@@ -14,21 +11,12 @@ export const EthWalletContainer = ({
|
||||
}) => {
|
||||
const { account } = useWeb3React();
|
||||
const { t } = useTranslation();
|
||||
const { appDispatch } = useAppState();
|
||||
const { open } = useWeb3ConnectStore();
|
||||
|
||||
if (!account) {
|
||||
return (
|
||||
<div className="w-full text-center">
|
||||
<Button
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('connectEthWallet')}
|
||||
</Button>
|
||||
<Button onClick={open}>{t('connectEthWallet')}</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,11 @@ import {
|
||||
import { Loader } from '@vegaprotocol/ui-toolkit';
|
||||
import colors from 'tailwindcss/colors';
|
||||
import { useBalances } from '../../lib/balances/balances-store';
|
||||
import { useEthereumConfig, useWeb3Disconnect } from '@vegaprotocol/web3';
|
||||
import {
|
||||
useEthereumConfig,
|
||||
useWeb3ConnectStore,
|
||||
useWeb3Disconnect,
|
||||
} from '@vegaprotocol/web3';
|
||||
import { getChainName } from '@vegaprotocol/web3';
|
||||
|
||||
const removeLeadingAddressSymbol = (key: string) => {
|
||||
@@ -189,6 +193,7 @@ export const EthWallet = () => {
|
||||
const pendingTxs = usePendingTransactions();
|
||||
const disconnect = useWeb3Disconnect(connector);
|
||||
const { config } = useEthereumConfig();
|
||||
const { open } = useWeb3ConnectStore();
|
||||
|
||||
return (
|
||||
<WalletCard>
|
||||
@@ -233,12 +238,7 @@ export const EthWallet = () => {
|
||||
) : (
|
||||
<Button
|
||||
fill={true}
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
onClick={open}
|
||||
data-testid="connect-to-eth-wallet-button"
|
||||
>
|
||||
{t('connectEthWalletToAssociate')}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ExternalLinks } from '@vegaprotocol/utils';
|
||||
import { ExternalLinks } from '@vegaprotocol/environment';
|
||||
|
||||
export const DownloadWalletPrompt = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
WalletDelegationFieldsFragment,
|
||||
} from './__generated__/Delegations';
|
||||
import { DelegationsDocument } from './__generated__/Delegations';
|
||||
import { isPartyNotFoundError } from '../../lib/party';
|
||||
|
||||
export const usePollForDelegations = () => {
|
||||
const { token: vegaToken } = useContracts();
|
||||
@@ -75,6 +76,7 @@ export const usePollForDelegations = () => {
|
||||
})
|
||||
.then((res) => {
|
||||
if (!mounted) return;
|
||||
|
||||
const canonisedDelegations = removePaginationWrapper(
|
||||
res.data.party?.delegationsConnection?.edges
|
||||
);
|
||||
@@ -203,6 +205,15 @@ export const usePollForDelegations = () => {
|
||||
setDelegatedNodes(delegatedAmounts);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
// if party isn't found, dont log to Sentry, just clear state as, user
|
||||
// will not have any delagations or accounts
|
||||
if (isPartyNotFoundError(err)) {
|
||||
setDelegations([]);
|
||||
setAccounts([]);
|
||||
setDelegatedNodes([]);
|
||||
setCurrentStakeAvailable(new BigNumber(0));
|
||||
return;
|
||||
}
|
||||
Sentry.captureException(err);
|
||||
// If query fails stop interval. Its almost certain that the query
|
||||
// will just continue to fail
|
||||
|
||||
@@ -25,20 +25,26 @@ export function Web3Connector({
|
||||
connectors,
|
||||
chainId,
|
||||
}: Web3ConnectorProps) {
|
||||
const { appState, appDispatch } = useAppState();
|
||||
const { open, close, isOpen } = useWeb3ConnectStore();
|
||||
|
||||
const setDialogOpen = useCallback(
|
||||
(isOpen: boolean) => {
|
||||
appDispatch({ type: AppStateActionType.SET_ETH_WALLET_OVERLAY, isOpen });
|
||||
if (isOpen) {
|
||||
open();
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
},
|
||||
[appDispatch]
|
||||
[open, close]
|
||||
);
|
||||
|
||||
const appChainId = Number(chainId);
|
||||
return (
|
||||
<>
|
||||
<Web3Content appChainId={appChainId}>{children}</Web3Content>
|
||||
<Web3ConnectDialog
|
||||
connectors={connectors}
|
||||
dialogOpen={appState.ethConnectOverlay}
|
||||
dialogOpen={isOpen}
|
||||
setDialogOpen={setDialogOpen}
|
||||
desiredChainId={appChainId}
|
||||
/>
|
||||
|
||||
@@ -34,9 +34,6 @@ export interface AppState {
|
||||
/** Whether or not the manage VEGA wallet overlay is open */
|
||||
vegaWalletManageOverlay: boolean;
|
||||
|
||||
/** Whether or not the connect to Ethereum wallet overlay is open */
|
||||
ethConnectOverlay: boolean;
|
||||
|
||||
/** Whether or not the transaction modal is open */
|
||||
transactionOverlay: boolean;
|
||||
/**
|
||||
@@ -57,7 +54,6 @@ export enum AppStateActionType {
|
||||
REFRESH_BALANCES,
|
||||
SET_VEGA_WALLET_OVERLAY,
|
||||
SET_VEGA_WALLET_MANAGE_OVERLAY,
|
||||
SET_ETH_WALLET_OVERLAY,
|
||||
SET_DRAWER,
|
||||
REFRESH_ASSOCIATED_BALANCES,
|
||||
SET_ASSOCIATION_BREAKDOWN,
|
||||
@@ -81,10 +77,6 @@ export type AppStateAction =
|
||||
type: AppStateActionType.SET_VEGA_WALLET_MANAGE_OVERLAY;
|
||||
isOpen: boolean;
|
||||
}
|
||||
| {
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY;
|
||||
isOpen: boolean;
|
||||
}
|
||||
| {
|
||||
type: AppStateActionType.SET_DRAWER;
|
||||
isOpen: boolean;
|
||||
|
||||
@@ -16,7 +16,6 @@ const initialAppState: AppState = {
|
||||
totalSupply: new BigNumber(0),
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
disconnectNotice: false,
|
||||
@@ -45,12 +44,6 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
|
||||
vegaWalletOverlay: action.isOpen ? false : state.vegaWalletOverlay,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_ETH_WALLET_OVERLAY: {
|
||||
return {
|
||||
...state,
|
||||
ethConnectOverlay: action.isOpen,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_DRAWER: {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -594,7 +594,9 @@
|
||||
"numberOfAgainstVotes": "Number of votes against",
|
||||
"yesPercentage": "Yes percentage",
|
||||
"noPercentage": "No percentage",
|
||||
"proposalTerms": "Proposal terms",
|
||||
"proposalJson": "Full proposal JSON",
|
||||
"proposalDetails": "Proposal details",
|
||||
"proposalDescription": "Description",
|
||||
"currentlySetTo": "Currently expected to ",
|
||||
"currently": "currently",
|
||||
"finalOutcomeMayDiffer": "Final outcome may differ",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export const PARTY_NOT_FOUND = 'failed to get party for ID';
|
||||
|
||||
export const isPartyNotFoundError = (error: { message: string }) => {
|
||||
if (error.message.includes(PARTY_NOT_FOUND)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -12,7 +12,8 @@ import { useRefreshAfterEpoch } from '../../hooks/use-refresh-after-epoch';
|
||||
import { ProposalsListItem } from '../proposals/components/proposals-list-item';
|
||||
import { ProtocolUpgradeProposalsListItem } from '../proposals/components/protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
|
||||
import Routes from '../routes';
|
||||
import { ExternalLinks, removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { ExternalLinks } from '@vegaprotocol/environment';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { useNodesQuery } from '../staking/home/__generated__/Nodes';
|
||||
import { useProposalsQuery } from '../proposals/proposals/__generated__/Proposals';
|
||||
import {
|
||||
|
||||
@@ -53,7 +53,6 @@ const mockAppState: AppState = {
|
||||
totalSupply: new BigNumber(65000000),
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
disconnectNotice: false,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { ProposalDescription } from './proposal-description';
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import classnames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
|
||||
export const ProposalDescription = ({
|
||||
description,
|
||||
}: {
|
||||
description: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDescription, setShowDescription] = useState(false);
|
||||
const showDescriptionIconClasses = classnames('mb-4', {
|
||||
'rotate-180': showDescription,
|
||||
});
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-description">
|
||||
<button
|
||||
onClick={() => setShowDescription(!showDescription)}
|
||||
data-testid="proposal-description-toggle"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<SubHeading title={t('proposalDescription')} />
|
||||
<div className={showDescriptionIconClasses}>
|
||||
<Icon name="chevron-down" size={8} />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{showDescription && (
|
||||
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
|
||||
<div className="p-2">
|
||||
<ReactMarkdown
|
||||
className="react-markdown-container"
|
||||
/* Prevents HTML embedded in the description from rendering */
|
||||
skipHtml={true}
|
||||
/* Stops users embedding images which could be used for tracking */
|
||||
disallowedElements={['img']}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{description}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</RoundedWrapper>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
-22
@@ -206,28 +206,6 @@ describe('Proposal header', () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Renders Freeform proposal - long rationale (105 chars) - details', () => {
|
||||
renderComponent(
|
||||
generateProposal({
|
||||
id: 'long',
|
||||
rationale: {
|
||||
title: '0x0',
|
||||
description:
|
||||
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
|
||||
},
|
||||
terms: {
|
||||
change: {
|
||||
__typename: 'NewFreeform',
|
||||
},
|
||||
},
|
||||
}),
|
||||
false
|
||||
);
|
||||
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
|
||||
/Class aptent/
|
||||
);
|
||||
});
|
||||
|
||||
// Remove once proposals have rationale and re-enable above tests
|
||||
it('Renders Freeform proposal - id for title', () => {
|
||||
renderComponent(
|
||||
|
||||
+1
-24
@@ -5,7 +5,6 @@ import { Heading, SubHeading } from '../../../../components/heading';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { truncateMiddle } from '../../../../lib/truncate-middle';
|
||||
import { CurrentProposalState } from '../current-proposal-state';
|
||||
import { ProposalInfoLabel } from '../proposal-info-label';
|
||||
@@ -23,12 +22,7 @@ export const ProposalHeader = ({
|
||||
let details: ReactNode;
|
||||
let proposalType = '';
|
||||
|
||||
let title = proposal?.rationale.title.trim();
|
||||
let description = proposal?.rationale.description.trim();
|
||||
if (title?.length === 0 && description && description.length > 0) {
|
||||
title = description;
|
||||
description = '';
|
||||
}
|
||||
const title = proposal?.rationale.title.trim();
|
||||
|
||||
const titleContent = shorten(title ?? '', 100);
|
||||
|
||||
@@ -145,23 +139,6 @@ export const ProposalHeader = ({
|
||||
{details}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{description && !isListItem && (
|
||||
<div data-testid="proposal-description">
|
||||
{/*<div className="uppercase mr-2">{t('ProposalDescription')}:</div>*/}
|
||||
<SubHeading title={t('ProposalDescription')} />
|
||||
<ReactMarkdown
|
||||
className="react-markdown-container"
|
||||
/* Prevents HTML embedded in the description from rendering */
|
||||
skipHtml={true}
|
||||
/* Stops users embedding images which could be used for tracking */
|
||||
disallowedElements={['img']}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{description}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { ProposalJson } from './proposal-json';
|
||||
+11
-11
@@ -1,15 +1,15 @@
|
||||
import classnames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import { useState } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
|
||||
export const ProposalTermsJson = ({
|
||||
terms,
|
||||
export const ProposalJson = ({
|
||||
proposal,
|
||||
}: {
|
||||
terms: PartialDeep<Schema.ProposalTerms>;
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
@@ -18,20 +18,20 @@ export const ProposalTermsJson = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<section>
|
||||
<section data-testid="proposal-json">
|
||||
<button
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
data-testid="proposal-terms-toggle"
|
||||
data-testid="proposal-json-toggle"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<SubHeading title={t('proposalTerms')} />
|
||||
<SubHeading title={t('proposalJson')} />
|
||||
<div className={showDetailsIconClasses}>
|
||||
<Icon name="chevron-down" size={8} />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{showDetails && <SyntaxHighlighter data={terms} />}
|
||||
{showDetails && <SyntaxHighlighter data={proposal} />}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { ProposalTermsJson } from './proposal-terms-json';
|
||||
@@ -0,0 +1 @@
|
||||
export { ProposalTerms } from './proposal-terms';
|
||||
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
RoundedWrapper,
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { BigNumber } from '../../../../lib/bignumber';
|
||||
import { SubHeading } from '../../../../components/heading';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useState } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
interface ProposalTermsProps {
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const getParsedValue = (value: unknown) => {
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
// Check if value is a number string - if so use bignumber to maintain precision
|
||||
if (/^\d+(\.\d+)?$/.test(value)) {
|
||||
return new BigNumber(value).toString();
|
||||
} else {
|
||||
// This would convert, for example, a JSON object ('{"key":"value"}')
|
||||
// into an actual JS object ({key: "value"})
|
||||
return JSON.parse(value);
|
||||
}
|
||||
} catch (error) {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
const RenderKeyValue = ({
|
||||
title,
|
||||
value,
|
||||
}: {
|
||||
title: string;
|
||||
value: string | number;
|
||||
}) => (
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
{title}
|
||||
{value}
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
);
|
||||
|
||||
const RenderArray = ({ title, array }: { title: string; array: unknown[] }) => {
|
||||
if (array.every((item) => typeof item === 'string')) {
|
||||
return <RenderKeyValue title={title} value={array.join(', ')} />;
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-2">{title}</div>
|
||||
{array.map((item, index) => (
|
||||
<div key={index}>
|
||||
<ProposalTermsRenderer data={item as Record<string, unknown>} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Working with 'unknown' type as a proposal's terms can be in many shapes
|
||||
const RenderTerm = ({ title, value }: { title: string; value: unknown }) => {
|
||||
const parsedValue = getParsedValue(value);
|
||||
|
||||
if (parsedValue === null || typeof parsedValue === 'boolean') {
|
||||
return <RenderKeyValue title={title} value={String(parsedValue)} />;
|
||||
} else if (
|
||||
typeof parsedValue === 'string' ||
|
||||
typeof parsedValue === 'number'
|
||||
) {
|
||||
return <RenderKeyValue title={title} value={parsedValue} />;
|
||||
} else if (typeof parsedValue === 'object') {
|
||||
if (Array.isArray(parsedValue)) {
|
||||
return <RenderArray title={title} array={parsedValue} />;
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<div className="my-2">{title}</div>
|
||||
<ProposalTermsRenderer
|
||||
data={parsedValue as Record<string, unknown>}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else if (parsedValue === undefined) {
|
||||
return <span>{'undefined'}</span>;
|
||||
} else {
|
||||
return <span>{String(parsedValue)}</span>;
|
||||
}
|
||||
};
|
||||
|
||||
const ProposalTermsRenderer = ({ data }: ProposalTermsProps) => {
|
||||
return (
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
{Object.keys(data)
|
||||
.filter(
|
||||
(key) =>
|
||||
!['__typename', 'closingDatetime', 'enactmentDatetime'].includes(
|
||||
key
|
||||
)
|
||||
)
|
||||
.map((key, index) => (
|
||||
<div key={index}>
|
||||
<RenderTerm title={key} value={data[key]} />
|
||||
</div>
|
||||
))}
|
||||
</RoundedWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProposalTerms = ({ data }: ProposalTermsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [showTerms, setShowTerms] = useState(false);
|
||||
const showTermsIconClasses = classnames('mb-4', {
|
||||
'rotate-180': showTerms,
|
||||
});
|
||||
|
||||
return (
|
||||
<section data-testid="proposal-terms">
|
||||
<button
|
||||
onClick={() => setShowTerms(!showTerms)}
|
||||
data-testid="proposal-terms-toggle"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<SubHeading title={t('proposalDetails')} />
|
||||
<div className={showTermsIconClasses}>
|
||||
<Icon name="chevron-down" size={8} />
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{showTerms && <ProposalTermsRenderer data={data} />}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@@ -25,8 +25,11 @@ jest.mock('../proposal-detail-header/proposal-header', () => ({
|
||||
jest.mock('../proposal-change-table', () => ({
|
||||
ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>,
|
||||
}));
|
||||
jest.mock('../proposal-terms-json', () => ({
|
||||
ProposalTermsJson: () => <div data-testid="proposal-terms-json"></div>,
|
||||
jest.mock('../proposal-json', () => ({
|
||||
ProposalJson: () => <div data-testid="proposal-json"></div>,
|
||||
}));
|
||||
jest.mock('../proposal-terms/proposal-terms', () => ({
|
||||
ProposalTerms: () => <div data-testid="proposal-terms"></div>,
|
||||
}));
|
||||
jest.mock('../proposal-votes-table', () => ({
|
||||
ProposalVotesTable: () => <div data-testid="proposal-votes-table"></div>,
|
||||
@@ -49,7 +52,8 @@ it('renders each section', async () => {
|
||||
render(<Proposal proposal={proposal as ProposalQuery['proposal']} />);
|
||||
expect(await screen.findByTestId('proposal-header')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-change-table')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-terms-json')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-json')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-terms')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument();
|
||||
|
||||
@@ -6,8 +6,10 @@ import { AsyncRenderer, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
|
||||
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import { ProposalDescription } from '../proposal-description';
|
||||
import { ProposalChangeTable } from '../proposal-change-table';
|
||||
import { ProposalTermsJson } from '../proposal-terms-json';
|
||||
import { ProposalJson } from '../proposal-json';
|
||||
import { ProposalTerms } from '../proposal-terms';
|
||||
import { ProposalVotesTable } from '../proposal-votes-table';
|
||||
import { VoteDetails } from '../vote-details';
|
||||
import { ListAsset } from '../list-asset';
|
||||
@@ -20,7 +22,7 @@ export enum ProposalType {
|
||||
PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER',
|
||||
PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM',
|
||||
}
|
||||
interface ProposalProps {
|
||||
export interface ProposalProps {
|
||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||
}
|
||||
|
||||
@@ -78,9 +80,11 @@ export const Proposal = ({ proposal }: ProposalProps) => {
|
||||
<AsyncRenderer data={params} loading={loading} error={error}>
|
||||
<section data-testid="proposal">
|
||||
<ProposalHeader proposal={proposal} isListItem={false} />
|
||||
|
||||
<div className="my-10">
|
||||
<ProposalChangeTable proposal={proposal} />
|
||||
</div>
|
||||
|
||||
{proposal.terms.change.__typename === 'NewAsset' &&
|
||||
proposal.terms.change.source.__typename === 'ERC20' &&
|
||||
proposal.id ? (
|
||||
@@ -90,7 +94,20 @@ export const Proposal = ({ proposal }: ProposalProps) => {
|
||||
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
|
||||
/>
|
||||
) : null}
|
||||
<div className="mb-12">
|
||||
|
||||
<div className="mb-4">
|
||||
<ProposalDescription description={proposal.rationale.description} />
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<ProposalTerms data={proposal.terms} />
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<ProposalJson proposal={proposal} />
|
||||
</div>
|
||||
|
||||
<div className="mb-10">
|
||||
<RoundedWrapper paddingBottom={true}>
|
||||
<VoteDetails
|
||||
proposal={proposal}
|
||||
@@ -102,10 +119,10 @@ export const Proposal = ({ proposal }: ProposalProps) => {
|
||||
/>
|
||||
</RoundedWrapper>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
|
||||
</div>
|
||||
<ProposalTermsJson terms={proposal.terms} />
|
||||
</section>
|
||||
</AsyncRenderer>
|
||||
);
|
||||
|
||||
@@ -8,12 +8,11 @@ import { ProposalsListFilter } from '../proposals-list-filter';
|
||||
import Routes from '../../../routes';
|
||||
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { createDocsLinks, ExternalLinks } from '@vegaprotocol/utils';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
|
||||
|
||||
interface ProposalsListProps {
|
||||
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
|
||||
@@ -36,7 +35,6 @@ export const ProposalsList = ({
|
||||
protocolUpgradeProposals,
|
||||
lastBlockHeight,
|
||||
}: ProposalsListProps) => {
|
||||
const { VEGA_DOCS_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const [filterString, setFilterString] = useState('');
|
||||
const sortedProposals = proposals.reduce(
|
||||
@@ -83,9 +81,9 @@ export const ProposalsList = ({
|
||||
marginBottom={false}
|
||||
title={t('pageTitleProposals')}
|
||||
/>
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<div className="xs:justify-self-end" data-testid="new-proposal-link">
|
||||
<ExternalLink href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}>
|
||||
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}>
|
||||
<Button variant="primary" size="sm">
|
||||
<div className="flex items-center gap-1">
|
||||
{t('NewProposal')}
|
||||
|
||||
+4
-3
@@ -2,9 +2,10 @@ import { render, screen } from '@testing-library/react';
|
||||
import { ProposalFormTerms } from './proposal-form-terms';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({
|
||||
VEGA_DOCS_URL: 'https://docs.vega.xyz',
|
||||
}),
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
DocsLinks: {
|
||||
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
|
||||
},
|
||||
}));
|
||||
|
||||
const renderComponent = () => {
|
||||
|
||||
@@ -5,8 +5,7 @@ import {
|
||||
ExternalLink,
|
||||
TextArea,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { createDocsLinks } from '@vegaprotocol/utils';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import type { UseFormRegisterReturn } from 'react-hook-form';
|
||||
|
||||
interface ProposalFormTermsProps {
|
||||
@@ -22,23 +21,20 @@ export const ProposalFormTerms = ({
|
||||
labelOverride,
|
||||
docsLink,
|
||||
}: ProposalFormTermsProps) => {
|
||||
const { VEGA_DOCS_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<FormGroup
|
||||
label={labelOverride || t('ProposalTerms')}
|
||||
labelFor="proposal-terms"
|
||||
>
|
||||
{docsLink && VEGA_DOCS_URL && (
|
||||
{docsLink && DocsLinks && (
|
||||
<div className="mt-[-4px] mb-2 text-sm font-light">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<ExternalLink
|
||||
href={`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${docsLink}`}
|
||||
href={`${DocsLinks.PROPOSALS_GUIDE}${docsLink}`}
|
||||
target="_blank"
|
||||
>
|
||||
{`${createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}${docsLink}`}
|
||||
{`${DocsLinks.PROPOSALS_GUIDE}${docsLink}`}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,6 @@ const mockAppState: AppState = {
|
||||
totalSupply: mockTotalSupply,
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
disconnectNotice: false,
|
||||
|
||||
@@ -10,9 +10,10 @@ import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({
|
||||
VEGA_DOCS_URL: 'https://docs.vega.xyz',
|
||||
}),
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
DocsLinks: {
|
||||
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
|
||||
},
|
||||
}));
|
||||
|
||||
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
useProposalSubmit,
|
||||
deadlineToRoundedHours,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import {
|
||||
ProposalFormDescription,
|
||||
ProposalFormSubheader,
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { ProposalMinRequirements } from '../../components/shared';
|
||||
import { AsyncRenderer, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { Heading } from '../../../../components/heading';
|
||||
import { createDocsLinks } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -42,7 +41,7 @@ export const ProposeFreeform = () => {
|
||||
NetworkParams.governance_proposal_freeform_minProposerBalance,
|
||||
NetworkParams.spam_protection_proposal_min_tokens,
|
||||
]);
|
||||
const { VEGA_DOCS_URL, VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
register,
|
||||
@@ -111,17 +110,13 @@ export const ProposeFreeform = () => {
|
||||
userAction={ProposalUserAction.CREATE}
|
||||
/>
|
||||
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<ExternalLink
|
||||
href={`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}
|
||||
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}</ExternalLink>
|
||||
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
+4
-3
@@ -10,9 +10,10 @@ import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({
|
||||
VEGA_DOCS_URL: 'https://docs.vega.xyz',
|
||||
}),
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
DocsLinks: {
|
||||
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
|
||||
},
|
||||
}));
|
||||
|
||||
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
|
||||
|
||||
+6
-13
@@ -1,10 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import {
|
||||
createDocsLinks,
|
||||
suitableForSyntaxHighlighter,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { suitableForSyntaxHighlighter } from '@vegaprotocol/utils';
|
||||
import { useNetworkParams } from '@vegaprotocol/network-parameters';
|
||||
import {
|
||||
getClosingTimestamp,
|
||||
@@ -12,7 +9,7 @@ import {
|
||||
useProposalSubmit,
|
||||
doesValueEquateToParam,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import {
|
||||
ProposalFormDescription,
|
||||
ProposalFormSubheader,
|
||||
@@ -84,7 +81,7 @@ export const ProposeNetworkParameter = () => {
|
||||
loading: networkParamsLoading,
|
||||
error: networkParamsError,
|
||||
} = useNetworkParams();
|
||||
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
register,
|
||||
@@ -175,17 +172,13 @@ export const ProposeNetworkParameter = () => {
|
||||
userAction={ProposalUserAction.CREATE}
|
||||
/>
|
||||
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<ExternalLink
|
||||
href={`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}
|
||||
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}</ExternalLink>
|
||||
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({
|
||||
VEGA_DOCS_URL: 'https://docs.vega.xyz',
|
||||
}),
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
DocsLinks: {
|
||||
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
|
||||
},
|
||||
}));
|
||||
|
||||
const newAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
useProposalSubmit,
|
||||
doesValueEquateToParam,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -53,7 +53,7 @@ export const ProposeNewAsset = () => {
|
||||
NetworkParams.governance_proposal_asset_minProposerBalance,
|
||||
NetworkParams.spam_protection_proposal_min_tokens,
|
||||
]);
|
||||
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
register,
|
||||
@@ -142,17 +142,13 @@ export const ProposeNewAsset = () => {
|
||||
userAction={ProposalUserAction.CREATE}
|
||||
/>
|
||||
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<ExternalLink
|
||||
href={`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}
|
||||
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}</ExternalLink>
|
||||
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({
|
||||
VEGA_DOCS_URL: 'https://docs.vega.xyz',
|
||||
}),
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
DocsLinks: {
|
||||
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
|
||||
},
|
||||
}));
|
||||
|
||||
const newMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
useProposalSubmit,
|
||||
doesValueEquateToParam,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -51,7 +51,7 @@ export const ProposeNewMarket = () => {
|
||||
NetworkParams.governance_proposal_market_minProposerBalance,
|
||||
NetworkParams.spam_protection_proposal_min_tokens,
|
||||
]);
|
||||
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
register,
|
||||
@@ -132,17 +132,13 @@ export const ProposeNewMarket = () => {
|
||||
userAction={ProposalUserAction.CREATE}
|
||||
/>
|
||||
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<ExternalLink
|
||||
href={`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}
|
||||
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}</ExternalLink>
|
||||
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@ import Routes from '../../routes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { Heading } from '../../../components/heading';
|
||||
import { createDocsLinks } from '@vegaprotocol/utils';
|
||||
|
||||
export const Propose = () => {
|
||||
const { VEGA_DOCS_URL, VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -15,14 +14,11 @@ export const Propose = () => {
|
||||
<section className="pb-6">
|
||||
<Heading title={t('NewProposal')} />
|
||||
<div className="text-sm">
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<p>
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<ExternalLink
|
||||
href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
|
||||
target="_blank"
|
||||
>
|
||||
{createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
|
||||
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE} target="_blank">
|
||||
{DocsLinks.PROPOSALS_GUIDE}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { Heading } from '../../../../components/heading';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
InputError,
|
||||
TextArea,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -39,7 +39,7 @@ export const ProposeRaw = () => {
|
||||
NetworkParams.governance_proposal_freeform_minProposerBalance,
|
||||
NetworkParams.spam_protection_proposal_min_tokens,
|
||||
]);
|
||||
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
register,
|
||||
@@ -79,14 +79,11 @@ export const ProposeRaw = () => {
|
||||
spamProtectionMin={params.spam_protection_proposal_min_tokens}
|
||||
/>
|
||||
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<ExternalLink
|
||||
href={createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
|
||||
target="_blank"
|
||||
>
|
||||
{createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE}
|
||||
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE} target="_blank">
|
||||
{DocsLinks.PROPOSALS_GUIDE}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
|
||||
+4
-3
@@ -10,9 +10,10 @@ import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({
|
||||
VEGA_DOCS_URL: 'https://docs.vega.xyz',
|
||||
}),
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
DocsLinks: {
|
||||
PROPOSALS_GUIDE: 'https://docs.vega.xyz/tutorials/proposals',
|
||||
},
|
||||
}));
|
||||
|
||||
const updateAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
useProposalSubmit,
|
||||
doesValueEquateToParam,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -51,7 +51,7 @@ export const ProposeUpdateAsset = () => {
|
||||
NetworkParams.governance_proposal_updateAsset_minProposerBalance,
|
||||
NetworkParams.spam_protection_proposal_min_tokens,
|
||||
]);
|
||||
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
register,
|
||||
@@ -132,17 +132,13 @@ export const ProposeUpdateAsset = () => {
|
||||
userAction={ProposalUserAction.CREATE}
|
||||
/>
|
||||
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<ExternalLink
|
||||
href={`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}
|
||||
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}</ExternalLink>
|
||||
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
+6
-10
@@ -7,8 +7,8 @@ import {
|
||||
getEnactmentTimestamp,
|
||||
useProposalSubmit,
|
||||
} from '@vegaprotocol/proposals';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { createDocsLinks, validateJson } from '@vegaprotocol/utils';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/utils';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
@@ -98,7 +98,7 @@ export const ProposeUpdateMarket = () => {
|
||||
const [selectedMarket, setSelectedMarket] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
register,
|
||||
@@ -181,17 +181,13 @@ export const ProposeUpdateMarket = () => {
|
||||
userAction={ProposalUserAction.CREATE}
|
||||
/>
|
||||
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<ExternalLink
|
||||
href={`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}
|
||||
href={`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${
|
||||
createDocsLinks(VEGA_DOCS_URL).PROPOSALS_GUIDE
|
||||
}${DOCS_LINK}`}</ExternalLink>
|
||||
>{`${DocsLinks.PROPOSALS_GUIDE}${DOCS_LINK}`}</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
+4
-2
@@ -25,7 +25,7 @@ const mockData = {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
ACCOUNT_TYPE_FEES_LIQUIDITY: {
|
||||
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
@@ -55,7 +55,9 @@ describe('EpochIndividualRewardsTable', () => {
|
||||
expect(
|
||||
getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES')
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId('ACCOUNT_TYPE_FEES_LIQUIDITY')).toBeInTheDocument();
|
||||
expect(
|
||||
getByTestId('ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS')
|
||||
).toBeInTheDocument();
|
||||
|
||||
+11
-11
@@ -24,7 +24,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
};
|
||||
|
||||
const reward3: RewardFieldsFragment = {
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '200',
|
||||
percentageOfTotal: '0.2',
|
||||
receivedAt: new Date(),
|
||||
@@ -34,7 +34,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
};
|
||||
|
||||
const reward4: RewardFieldsFragment = {
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '100',
|
||||
percentageOfTotal: '0.1',
|
||||
receivedAt: new Date(),
|
||||
@@ -44,7 +44,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
};
|
||||
|
||||
const reward5: RewardFieldsFragment = {
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '150',
|
||||
percentageOfTotal: '0.15',
|
||||
receivedAt: new Date(),
|
||||
@@ -105,7 +105,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
@@ -172,7 +172,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
amount: '200',
|
||||
percentageOfTotal: '0.2',
|
||||
},
|
||||
@@ -202,7 +202,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
@@ -237,7 +237,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
amount: '100',
|
||||
percentageOfTotal: '0.1',
|
||||
},
|
||||
@@ -285,7 +285,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
amount: '150',
|
||||
percentageOfTotal: '0.15',
|
||||
},
|
||||
@@ -320,7 +320,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
amount: '200',
|
||||
percentageOfTotal: '0.2',
|
||||
},
|
||||
@@ -350,7 +350,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
@@ -395,7 +395,7 @@ describe('generateEpochIndividualRewardsList', () => {
|
||||
amount: '0',
|
||||
percentageOfTotal: '0',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
amount: '100',
|
||||
percentageOfTotal: '0.1',
|
||||
},
|
||||
|
||||
+18
-1
@@ -79,7 +79,24 @@ export const generateEpochIndividualRewardsList = ({
|
||||
epoch?.rewards.push(asset);
|
||||
}
|
||||
|
||||
asset.rewardTypes[rewardType] = { amount, percentageOfTotal };
|
||||
if (!asset.rewardTypes[rewardType]) {
|
||||
asset.rewardTypes[rewardType] = { amount, percentageOfTotal };
|
||||
} else {
|
||||
const previousAmount = asset.rewardTypes[rewardType]?.amount;
|
||||
const previousPercentageOfTotal =
|
||||
asset.rewardTypes[rewardType]?.percentageOfTotal;
|
||||
|
||||
asset.rewardTypes[rewardType] = {
|
||||
amount: previousAmount
|
||||
? new BigNumber(previousAmount).plus(amount).toString()
|
||||
: amount,
|
||||
percentageOfTotal: previousPercentageOfTotal
|
||||
? new BigNumber(previousPercentageOfTotal)
|
||||
.plus(percentageOfTotal)
|
||||
.toString()
|
||||
: percentageOfTotal,
|
||||
};
|
||||
}
|
||||
|
||||
// totalAmount is the sum of all rewardTypes amounts
|
||||
asset.totalAmount = Object.values(asset.rewardTypes).reduce(
|
||||
|
||||
+4
-2
@@ -29,7 +29,7 @@ const rewardsList = [
|
||||
amount: '0',
|
||||
},
|
||||
{
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '0',
|
||||
},
|
||||
{
|
||||
@@ -78,7 +78,9 @@ describe('EpochTotalRewardsTable', () => {
|
||||
expect(
|
||||
getByTestId('ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES')
|
||||
).toBeInTheDocument();
|
||||
expect(getByTestId('ACCOUNT_TYPE_FEES_LIQUIDITY')).toBeInTheDocument();
|
||||
expect(
|
||||
getByTestId('ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByTestId('ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS')
|
||||
).toBeInTheDocument();
|
||||
|
||||
+21
-15
@@ -162,9 +162,10 @@ describe('generateEpochAssetRewardsList', () => {
|
||||
},
|
||||
],
|
||||
[
|
||||
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
{
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '0',
|
||||
},
|
||||
],
|
||||
@@ -227,7 +228,7 @@ describe('generateEpochAssetRewardsList', () => {
|
||||
node: {
|
||||
epoch: 2,
|
||||
assetId: '1',
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '5',
|
||||
},
|
||||
},
|
||||
@@ -287,9 +288,10 @@ describe('generateEpochAssetRewardsList', () => {
|
||||
},
|
||||
],
|
||||
[
|
||||
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
{
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '0',
|
||||
},
|
||||
],
|
||||
@@ -351,9 +353,10 @@ describe('generateEpochAssetRewardsList', () => {
|
||||
},
|
||||
],
|
||||
[
|
||||
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
{
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '5',
|
||||
},
|
||||
],
|
||||
@@ -416,7 +419,7 @@ describe('generateEpochAssetRewardsList', () => {
|
||||
node: {
|
||||
epoch: 2,
|
||||
assetId: '1',
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '6',
|
||||
},
|
||||
},
|
||||
@@ -424,7 +427,7 @@ describe('generateEpochAssetRewardsList', () => {
|
||||
node: {
|
||||
epoch: 2,
|
||||
assetId: '1',
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType: AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '27',
|
||||
},
|
||||
},
|
||||
@@ -497,9 +500,10 @@ describe('generateEpochAssetRewardsList', () => {
|
||||
},
|
||||
],
|
||||
[
|
||||
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
{
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '33',
|
||||
},
|
||||
],
|
||||
@@ -561,9 +565,10 @@ describe('generateEpochAssetRewardsList', () => {
|
||||
},
|
||||
],
|
||||
[
|
||||
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
{
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '0',
|
||||
},
|
||||
],
|
||||
@@ -637,9 +642,10 @@ describe('generateEpochAssetRewardsList', () => {
|
||||
},
|
||||
],
|
||||
[
|
||||
AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
{
|
||||
rewardType: AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY,
|
||||
rewardType:
|
||||
AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES,
|
||||
amount: '0',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
ExternalLink,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { createDocsLinks } from '@vegaprotocol/utils';
|
||||
import {
|
||||
useNetworkParams,
|
||||
NetworkParams,
|
||||
@@ -21,7 +20,7 @@ import { EpochCountdown } from '../../../components/epoch-countdown';
|
||||
import { Heading, SubHeading } from '../../../components/heading';
|
||||
import { EpochIndividualRewards } from '../epoch-individual-rewards/epoch-individual-rewards';
|
||||
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { ConnectToSeeRewards } from '../connect-to-see-rewards';
|
||||
import { EpochTotalRewards } from '../epoch-total-rewards/epoch-total-rewards';
|
||||
|
||||
@@ -29,7 +28,6 @@ type RewardsView = 'total' | 'individual';
|
||||
|
||||
export const RewardsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { VEGA_DOCS_URL } = useEnvironment();
|
||||
const { pubKey, pubKeys } = useVegaWallet();
|
||||
const [toggleRewardsView, setToggleRewardsView] =
|
||||
useState<RewardsView>('total');
|
||||
@@ -68,9 +66,9 @@ export const RewardsPage = () => {
|
||||
<Heading title={t('pageTitleRewards')} />
|
||||
<p className="mb-12">
|
||||
{t('rewardsIntro')}{' '}
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<ExternalLink
|
||||
href={createDocsLinks(VEGA_DOCS_URL).REWARDS_GUIDE}
|
||||
href={DocsLinks.REWARDS_GUIDE}
|
||||
target="_blank"
|
||||
data-testid="rewards-guide-link"
|
||||
className="text-white"
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ export const RowAccountTypes = {
|
||||
columnTitle: 'rewardsColPriceMakingHeader',
|
||||
description: 'rewardsColPriceMakingTooltip',
|
||||
},
|
||||
[AccountType.ACCOUNT_TYPE_FEES_LIQUIDITY]: {
|
||||
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
|
||||
columnTitle: 'rewardsColLiquidityProvisionHeader',
|
||||
description: 'rewardsColLiquidityProvisionTooltip',
|
||||
},
|
||||
|
||||
@@ -229,7 +229,7 @@ const redirects = [
|
||||
const routerConfig = [
|
||||
{
|
||||
path: Routes.HOME,
|
||||
element: <LazyHome name="Token" />,
|
||||
element: <LazyHome name="Home" />,
|
||||
},
|
||||
{
|
||||
path: Routes.PROPOSALS,
|
||||
@@ -291,7 +291,7 @@ const routerConfig = [
|
||||
// Not lazy as loaded when a user first hits the site
|
||||
children: [
|
||||
{
|
||||
element: <Home name="Home" />,
|
||||
element: <Home name="Token" />,
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
@@ -325,8 +325,11 @@ const routerConfig = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{ path: 'associate', element: <LazyStakingAssociate /> },
|
||||
{ path: 'disassociate', element: <LazyStakingDisassociate /> },
|
||||
{ path: 'associate', element: <LazyStakingAssociate name="Associate" /> },
|
||||
{
|
||||
path: 'disassociate',
|
||||
element: <LazyStakingDisassociate name="Disassociate" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import { StakingWalletsContainer } from './components/staking-wallets-container/staking-wallets-container';
|
||||
import { StakingWalletsContainer } from './components/staking-wallets-container';
|
||||
import { AssociatePage } from './associate-page';
|
||||
import { AssociatePageNoVega } from './associate-page-no-vega';
|
||||
import { Heading } from '../../../components/heading';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
import type { RouteChildProps } from '../../index';
|
||||
|
||||
export const AssociateContainer = () => {
|
||||
export const AssociateContainer = ({ name }: RouteChildProps) => {
|
||||
useDocumentTitle(name);
|
||||
const { t } = useTranslation();
|
||||
const { config } = useEthereumConfig();
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ jest.mock('@vegaprotocol/wallet', () => ({
|
||||
}));
|
||||
|
||||
const renderComponent = () => {
|
||||
return render(<Disassociate />);
|
||||
return render(<Disassociate name="Disassociate" />);
|
||||
};
|
||||
|
||||
describe('Disassociate', () => {
|
||||
|
||||
@@ -3,10 +3,12 @@ import { useWeb3React } from '@web3-react/core';
|
||||
import { EthConnectPrompt } from '../../../components/eth-connect-prompt';
|
||||
import { DisassociatePage } from './components/disassociate-page';
|
||||
import { Heading } from '../../../components/heading';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDocumentTitle } from '../../../hooks/use-document-title';
|
||||
import type { RouteChildProps } from '../../index';
|
||||
|
||||
export const DisassociateContainer = () => {
|
||||
export const DisassociateContainer = ({ name }: RouteChildProps) => {
|
||||
useDocumentTitle(name);
|
||||
const { t } = useTranslation();
|
||||
const { account } = useWeb3React();
|
||||
const { pubKey } = useVegaWallet();
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EpochData } from './epoch-data';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { createDocsLinks } from '@vegaprotocol/utils';
|
||||
import { Heading } from '../../../components/heading';
|
||||
import React from 'react';
|
||||
|
||||
export const Staking = () => {
|
||||
const { t } = useTranslation();
|
||||
const { VEGA_DOCS_URL } = useEnvironment();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -16,9 +14,9 @@ export const Staking = () => {
|
||||
<section>
|
||||
<p className="mb-12">
|
||||
{t('stakingIntro')}{' '}
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<ExternalLink
|
||||
href={createDocsLinks(VEGA_DOCS_URL).STAKING_GUIDE}
|
||||
href={DocsLinks.STAKING_GUIDE}
|
||||
target="_blank"
|
||||
data-testid="staking-guide-link"
|
||||
className="text-white"
|
||||
|
||||
+69
-58
@@ -131,20 +131,25 @@ export const ConsensusValidatorsTable = ({
|
||||
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
|
||||
const thirdOfTotalStake = useMemo(
|
||||
() => new BigNumber(totalStake).dividedBy(3),
|
||||
[totalStake]
|
||||
);
|
||||
|
||||
const nodes = useMemo(() => {
|
||||
if (!data) return [];
|
||||
let canonisedNodes = data
|
||||
.sort((a, b) => {
|
||||
const aVotingPower = new BigNumber(a.rankingScore.votingPower);
|
||||
const bVotingPower = new BigNumber(b.rankingScore.votingPower);
|
||||
return bVotingPower.minus(aVotingPower).toNumber();
|
||||
const aStakedTotal = new BigNumber(a.stakedTotal);
|
||||
const bStakedTotal = new BigNumber(b.stakedTotal);
|
||||
return bStakedTotal.minus(aStakedTotal).toNumber();
|
||||
})
|
||||
.map((node, index) => {
|
||||
const votingPowerRanking = index + 1;
|
||||
const stakedTotalRanking = index + 1;
|
||||
|
||||
return {
|
||||
...node,
|
||||
votingPowerRanking,
|
||||
stakedTotalRanking,
|
||||
};
|
||||
})
|
||||
.map(
|
||||
@@ -157,7 +162,7 @@ export const ConsensusValidatorsTable = ({
|
||||
stakedTotal,
|
||||
rankingScore: { stakeScore, votingPower },
|
||||
pendingStake,
|
||||
votingPowerRanking,
|
||||
stakedTotalRanking,
|
||||
stakedByUser,
|
||||
pendingUserStake,
|
||||
userStakeShare,
|
||||
@@ -175,7 +180,7 @@ export const ConsensusValidatorsTable = ({
|
||||
|
||||
return {
|
||||
id,
|
||||
[ValidatorFields.RANKING_INDEX]: votingPowerRanking,
|
||||
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
|
||||
[ValidatorFields.VALIDATOR]: {
|
||||
avatarUrl: logo,
|
||||
name,
|
||||
@@ -236,16 +241,12 @@ export const ConsensusValidatorsTable = ({
|
||||
return canonisedNodes;
|
||||
}
|
||||
|
||||
// The point of identifying and hiding the group that could halt the network
|
||||
// is that we assume the top 1/3 of stake is held by considerably less than
|
||||
// 1/3 of the validators and we really want people not to stake any more to
|
||||
// that group, because we want to make it require as many difference
|
||||
// validators to collude as possible to halt the network, so we hide them.
|
||||
const { topThird, remaining } = canonisedNodes.reduce(
|
||||
(acc, node) => {
|
||||
if (acc.cumulativeScore < 100 / 3) {
|
||||
acc.cumulativeScore += parseFloat(
|
||||
node[ValidatorFields.NORMALISED_VOTING_POWER]
|
||||
if (acc.cumulativeScore.isLessThan(thirdOfTotalStake)) {
|
||||
const prev = acc.cumulativeScore;
|
||||
acc.cumulativeScore = prev.plus(
|
||||
new BigNumber(node[ValidatorFields.STAKE])
|
||||
);
|
||||
acc.topThird.push(node);
|
||||
return acc;
|
||||
@@ -253,61 +254,70 @@ export const ConsensusValidatorsTable = ({
|
||||
acc.remaining.push(node);
|
||||
return acc;
|
||||
},
|
||||
{ topThird: [], remaining: [], cumulativeScore: 0 } as {
|
||||
{ topThird: [], remaining: [], cumulativeScore: new BigNumber(0) } as {
|
||||
topThird: CanonisedConsensusNodeProps[];
|
||||
remaining: CanonisedConsensusNodeProps[];
|
||||
cumulativeScore: number;
|
||||
cumulativeScore: BigNumber;
|
||||
}
|
||||
);
|
||||
|
||||
// We need to combine the top third of validators into a single node, this
|
||||
// way the combined values can be passed to AG grid so that the combined cell's
|
||||
// values are correct for ordering.
|
||||
const combinedTopThird = topThird.reduce((acc, node) => {
|
||||
const {
|
||||
[ValidatorFields.STAKE]: stake,
|
||||
[ValidatorFields.STAKE_SHARE]: stakeShare,
|
||||
[ValidatorFields.PENDING_STAKE]: pendingStake,
|
||||
[ValidatorFields.NORMALISED_VOTING_POWER]: normalisedVotingPower,
|
||||
[ValidatorFields.TOTAL_PENALTIES]: totalPenalties,
|
||||
} = node;
|
||||
const combinedTopThird = topThird.reduce(
|
||||
(acc, node) => {
|
||||
const {
|
||||
[ValidatorFields.STAKE]: stake,
|
||||
[ValidatorFields.STAKE_SHARE]: stakeShare,
|
||||
[ValidatorFields.PENDING_STAKE]: pendingStake,
|
||||
[ValidatorFields.NORMALISED_VOTING_POWER]: normalisedVotingPower,
|
||||
[ValidatorFields.TOTAL_PENALTIES]: totalPenalties,
|
||||
} = node;
|
||||
|
||||
const {
|
||||
[ValidatorFields.STAKE]: accStake,
|
||||
[ValidatorFields.STAKE_SHARE]: accStakeShare,
|
||||
[ValidatorFields.PENDING_STAKE]: accPendingStake,
|
||||
[ValidatorFields.NORMALISED_VOTING_POWER]: accNormalisedVotingPower,
|
||||
[ValidatorFields.TOTAL_PENALTIES]: accTotalPenalties,
|
||||
} = acc;
|
||||
const {
|
||||
[ValidatorFields.STAKE]: accStake,
|
||||
[ValidatorFields.STAKE_SHARE]: accStakeShare,
|
||||
[ValidatorFields.PENDING_STAKE]: accPendingStake,
|
||||
[ValidatorFields.NORMALISED_VOTING_POWER]: accNormalisedVotingPower,
|
||||
[ValidatorFields.TOTAL_PENALTIES]: accTotalPenalties,
|
||||
} = acc;
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[ValidatorFields.STAKE]: toBigNum(accStake, decimals)
|
||||
.plus(toBigNum(stake, decimals))
|
||||
.toString(),
|
||||
[ValidatorFields.STAKE_SHARE]: formatNumberPercentage(
|
||||
new BigNumber(parseFloat(accStakeShare)).plus(
|
||||
new BigNumber(parseFloat(stakeShare))
|
||||
return {
|
||||
...acc,
|
||||
[ValidatorFields.STAKE]: new BigNumber(accStake)
|
||||
.plus(new BigNumber(stake))
|
||||
.toString(),
|
||||
[ValidatorFields.STAKE_SHARE]: formatNumberPercentage(
|
||||
new BigNumber(parseFloat(accStakeShare)).plus(
|
||||
new BigNumber(parseFloat(stakeShare))
|
||||
),
|
||||
2
|
||||
),
|
||||
2
|
||||
),
|
||||
[ValidatorFields.PENDING_STAKE]: toBigNum(accPendingStake, decimals)
|
||||
.plus(toBigNum(pendingStake, decimals))
|
||||
.toString(),
|
||||
[ValidatorFields.NORMALISED_VOTING_POWER]: formatNumberPercentage(
|
||||
new BigNumber(parseFloat(accNormalisedVotingPower)).plus(
|
||||
new BigNumber(parseFloat(normalisedVotingPower))
|
||||
[ValidatorFields.PENDING_STAKE]: toBigNum(accPendingStake, decimals)
|
||||
.plus(toBigNum(pendingStake, decimals))
|
||||
.toString(),
|
||||
[ValidatorFields.NORMALISED_VOTING_POWER]: formatNumberPercentage(
|
||||
new BigNumber(parseFloat(accNormalisedVotingPower)).plus(
|
||||
new BigNumber(parseFloat(normalisedVotingPower))
|
||||
),
|
||||
2
|
||||
),
|
||||
2
|
||||
),
|
||||
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
|
||||
new BigNumber(parseFloat(accTotalPenalties)).plus(
|
||||
new BigNumber(parseFloat(totalPenalties))
|
||||
[ValidatorFields.TOTAL_PENALTIES]: formatNumberPercentage(
|
||||
new BigNumber(parseFloat(accTotalPenalties)).plus(
|
||||
new BigNumber(parseFloat(totalPenalties))
|
||||
),
|
||||
2
|
||||
),
|
||||
2
|
||||
),
|
||||
};
|
||||
});
|
||||
};
|
||||
},
|
||||
{
|
||||
[ValidatorFields.STAKE]: '0',
|
||||
[ValidatorFields.STAKE_SHARE]: '0',
|
||||
[ValidatorFields.PENDING_STAKE]: '0',
|
||||
[ValidatorFields.NORMALISED_VOTING_POWER]: '0',
|
||||
[ValidatorFields.TOTAL_PENALTIES]: '0',
|
||||
}
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -322,6 +332,7 @@ export const ConsensusValidatorsTable = ({
|
||||
decimals,
|
||||
hideTopThird,
|
||||
previousEpochData,
|
||||
thirdOfTotalStake,
|
||||
totalStake,
|
||||
validatorsView,
|
||||
]);
|
||||
@@ -352,6 +363,7 @@ export const ConsensusValidatorsTable = ({
|
||||
headerTooltip: t('StakeDescription').toString(),
|
||||
cellRenderer: TotalStakeRenderer,
|
||||
width: 120,
|
||||
sort: 'desc',
|
||||
},
|
||||
{
|
||||
field: ValidatorFields.PENDING_STAKE,
|
||||
@@ -373,7 +385,6 @@ export const ConsensusValidatorsTable = ({
|
||||
headerTooltip: t('NormalisedVotingPowerDescription').toString(),
|
||||
cellRenderer: VotingPowerRenderer,
|
||||
width: 200,
|
||||
sort: 'desc',
|
||||
},
|
||||
{
|
||||
field: ValidatorFields.TOTAL_PENALTIES,
|
||||
|
||||
+9
-9
@@ -57,16 +57,16 @@ export const StandbyPendingValidatorsTable = ({
|
||||
|
||||
return data
|
||||
.sort((a, b) => {
|
||||
const aVotingPower = new BigNumber(a.rankingScore.votingPower);
|
||||
const bVotingPower = new BigNumber(b.rankingScore.votingPower);
|
||||
return bVotingPower.minus(aVotingPower).toNumber();
|
||||
const aStakedTotal = new BigNumber(a.stakedTotal);
|
||||
const bStakedTotal = new BigNumber(b.stakedTotal);
|
||||
return bStakedTotal.minus(aStakedTotal).toNumber();
|
||||
})
|
||||
.map((node, index) => {
|
||||
const votingPowerRanking = index + 1;
|
||||
const stakedTotalRanking = index + 1;
|
||||
|
||||
return {
|
||||
...node,
|
||||
votingPowerRanking,
|
||||
stakedTotalRanking,
|
||||
};
|
||||
})
|
||||
.map(
|
||||
@@ -79,7 +79,7 @@ export const StandbyPendingValidatorsTable = ({
|
||||
stakedTotal,
|
||||
rankingScore: { stakeScore },
|
||||
pendingStake,
|
||||
votingPowerRanking,
|
||||
stakedTotalRanking,
|
||||
stakedByUser,
|
||||
pendingUserStake,
|
||||
userStakeShare,
|
||||
@@ -125,7 +125,7 @@ export const StandbyPendingValidatorsTable = ({
|
||||
|
||||
return {
|
||||
id,
|
||||
[ValidatorFields.RANKING_INDEX]: votingPowerRanking,
|
||||
[ValidatorFields.RANKING_INDEX]: stakedTotalRanking,
|
||||
[ValidatorFields.VALIDATOR]: {
|
||||
avatarUrl,
|
||||
name,
|
||||
@@ -166,7 +166,7 @@ export const StandbyPendingValidatorsTable = ({
|
||||
: undefined,
|
||||
[ValidatorFields.PENDING_USER_STAKE]: pendingUserStake,
|
||||
[ValidatorFields.USER_STAKE_SHARE]: userStakeShare
|
||||
? formatNumberPercentage(new BigNumber(userStakeShare))
|
||||
? formatNumberPercentage(new BigNumber(userStakeShare), 2)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
@@ -210,6 +210,7 @@ export const StandbyPendingValidatorsTable = ({
|
||||
headerTooltip: t('StakeDescription').toString(),
|
||||
cellRenderer: TotalStakeRenderer,
|
||||
width: 120,
|
||||
sort: 'desc',
|
||||
},
|
||||
{
|
||||
field: ValidatorFields.PENDING_STAKE,
|
||||
@@ -233,7 +234,6 @@ export const StandbyPendingValidatorsTable = ({
|
||||
}),
|
||||
cellRenderer: StakeNeededForPromotionRenderer,
|
||||
width: 210,
|
||||
sort: 'asc',
|
||||
},
|
||||
{
|
||||
field: ValidatorFields.TOTAL_PENALTIES,
|
||||
|
||||
@@ -2,12 +2,8 @@ import { useMemo, useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
createDocsLinks,
|
||||
removePaginationWrapper,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { removePaginationWrapper, toBigNum } from '@vegaprotocol/utils';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { Link as UTLink, Toggle } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { formatNumber } from '../../../../lib/format-number';
|
||||
@@ -42,7 +38,6 @@ export const ValidatorTables = ({
|
||||
previousEpochData,
|
||||
}: ValidatorsTableProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { VEGA_DOCS_URL } = useEnvironment();
|
||||
const {
|
||||
appState: { decimals },
|
||||
} = useAppState();
|
||||
@@ -213,12 +208,12 @@ export const ValidatorTables = ({
|
||||
<>
|
||||
<SubHeading title={t('status-pending')} />
|
||||
<p>
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<>
|
||||
<span>{t('pendingDescription1')} </span>
|
||||
<span>
|
||||
<UTLink
|
||||
href={createDocsLinks(VEGA_DOCS_URL).STAKING_GUIDE}
|
||||
href={DocsLinks.STAKING_GUIDE}
|
||||
target="_blank"
|
||||
data-testid="validator-forum-link"
|
||||
>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { usePreviousEpochQuery } from '../__generated__/PreviousEpoch';
|
||||
import type { ReactElement } from 'react';
|
||||
import type { StakingQuery } from '../__generated__/Staking';
|
||||
import type { PreviousEpochQuery } from '../__generated__/PreviousEpoch';
|
||||
import { isPartyNotFoundError } from '../../../lib/party';
|
||||
|
||||
// TODO should only request a single node. When migrating from deprecated APIs we should address this.
|
||||
|
||||
@@ -28,6 +29,7 @@ export const NodeContainer = ({
|
||||
const { t } = useTranslation();
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { delegationsPagination } = ENV;
|
||||
|
||||
const { data, loading, error, refetch } = useStakingQuery({
|
||||
variables: {
|
||||
partyId: pubKey || '',
|
||||
@@ -37,7 +39,9 @@ export const NodeContainer = ({
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
errorPolicy: 'all',
|
||||
});
|
||||
|
||||
const { data: previousEpochData } = usePreviousEpochQuery({
|
||||
variables: {
|
||||
epochId: (Number(data?.epoch.id) - 1).toString(),
|
||||
@@ -47,7 +51,7 @@ export const NodeContainer = ({
|
||||
|
||||
useRefreshAfterEpoch(data?.epoch.timestamps.expiry, refetch);
|
||||
|
||||
if (error) {
|
||||
if (error && !isPartyNotFoundError(error)) {
|
||||
return (
|
||||
<Callout intent={Intent.Danger} title={t('Something went wrong')}>
|
||||
<pre>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { createDocsLinks, ExternalLinks, toBigNum } from '@vegaprotocol/utils';
|
||||
import {
|
||||
useEnvironment,
|
||||
DocsLinks,
|
||||
ExternalLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
Link as UTLink,
|
||||
@@ -64,7 +68,7 @@ export const ValidatorTable = ({
|
||||
stakedTotal,
|
||||
previousEpochData,
|
||||
}: ValidatorTableProps) => {
|
||||
const { ETHERSCAN_URL, VEGA_DOCS_URL } = useEnvironment();
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
appState: { decimals },
|
||||
@@ -90,9 +94,9 @@ export const ValidatorTable = ({
|
||||
<>
|
||||
<p className="mb-12">
|
||||
{t('validatorFormIntro')}{' '}
|
||||
{VEGA_DOCS_URL && (
|
||||
{DocsLinks && (
|
||||
<ExternalLink
|
||||
href={createDocsLinks(VEGA_DOCS_URL).STAKING_GUIDE}
|
||||
href={DocsLinks.STAKING_GUIDE}
|
||||
target="_blank"
|
||||
data-testid="validator-table-staking-guide-link"
|
||||
className="text-white"
|
||||
|
||||
@@ -60,9 +60,14 @@ export const getOverstakingPenalty = (
|
||||
}
|
||||
|
||||
return formatNumberPercentage(
|
||||
new BigNumber(1)
|
||||
.minus(new BigNumber(validatorScore).dividedBy(new BigNumber(stakeScore)))
|
||||
.times(100),
|
||||
BigNumber.max(
|
||||
new BigNumber(1)
|
||||
.minus(
|
||||
new BigNumber(validatorScore).dividedBy(new BigNumber(stakeScore))
|
||||
)
|
||||
.times(100),
|
||||
new BigNumber(0)
|
||||
),
|
||||
2
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Heading, SubHeading } from '../../components/heading';
|
||||
import { ExternalLinks, toBigNum } from '@vegaprotocol/utils';
|
||||
import { ExternalLinks } from '@vegaprotocol/environment';
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import { useAppState } from '../../contexts/app-state/app-state-context';
|
||||
import { useDocumentTitle } from '../../hooks/use-document-title';
|
||||
import type { RouteChildProps } from '..';
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
line-height: calc(min(var(--ag-line-height, 26px), 26px) - 4px);
|
||||
}
|
||||
|
||||
.vega-ag-grid .ag-row {
|
||||
border-width: 1px 0;
|
||||
border-bottom: 1px solid transparent;
|
||||
.vega-ag-grid .ag-row,
|
||||
.vega-ag-grid .ag-cell {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* Dark variables */
|
||||
@@ -54,7 +54,6 @@
|
||||
--ag-header-column-separator-color: theme(colors.neutral[600]);
|
||||
--ag-row-border-color: theme(colors.black);
|
||||
--ag-row-hover-color: theme(colors.neutral[800]);
|
||||
--ag-font-size: 12px;
|
||||
}
|
||||
|
||||
.validators-table .ag-theme-balham-dark .ag-body-horizontal-scroll {
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ import {
|
||||
displayChange,
|
||||
} from '@vegaprotocol/liquidity';
|
||||
|
||||
import type { Candle } from '@vegaprotocol/market-list';
|
||||
import { marketCandlesProvider } from '@vegaprotocol/market-list';
|
||||
import type { Candle } from '@vegaprotocol/markets';
|
||||
import { marketCandlesProvider } from '@vegaprotocol/markets';
|
||||
|
||||
const THROTTLE_UPDATE_TIME = 500;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ module.exports = defineConfig({
|
||||
chromeWebSecurity: false,
|
||||
projectId: 'et4snf',
|
||||
defaultCommandTimeout: 10000,
|
||||
viewportWidth: 1440,
|
||||
viewportWidth: 1800,
|
||||
viewportHeight: 900,
|
||||
responseTimeout: 50000,
|
||||
requestTimeout: 20000,
|
||||
|
||||
@@ -318,10 +318,11 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
.should('have.text', product.settlementAsset.symbol);
|
||||
|
||||
// 6001-MARK-020
|
||||
cy.get(rowSelector)
|
||||
cy.get('.ag-pinned-right-cols-container')
|
||||
.find('[col-id="market-actions"]')
|
||||
.first()
|
||||
.find('[col-id="id"]')
|
||||
.should('have.text', settledMarket.id);
|
||||
.find('button svg')
|
||||
.should('exist');
|
||||
});
|
||||
|
||||
// test market list for market in terminated state
|
||||
@@ -373,5 +374,33 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
'have.text',
|
||||
`Asset details - ${settlementAsset.symbol}`
|
||||
);
|
||||
|
||||
cy.get('[data-testid="dialog-close"]').click();
|
||||
});
|
||||
|
||||
it('can open row actions', () => {
|
||||
cy.get('.ag-pinned-right-cols-container')
|
||||
.find('[col-id="market-actions"]')
|
||||
.first()
|
||||
.find('button')
|
||||
.click();
|
||||
|
||||
const dropdownContent = '[data-testid="market-actions-content"]';
|
||||
const dropdownContentItem = '[role="menuitem"]';
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(0)
|
||||
// Cannot click the copy button as it falls back to window.prompt, blocking the test.
|
||||
.should('have.text', 'Copy Market ID');
|
||||
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(1)
|
||||
.find('a')
|
||||
.then(($el) => {
|
||||
const href = $el.attr('href');
|
||||
expect(/\/markets\/0/.test(href || '')).to.equal(true);
|
||||
})
|
||||
.should('have.text', 'View on Explorer');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,10 +108,9 @@ describe('deposit actions', { tags: '@smoke' }, () => {
|
||||
|
||||
it('Deposit to trade is visble', () => {
|
||||
cy.getByTestId('Collateral').click();
|
||||
cy.contains('[data-testid="deposit"]', 'Deposit to trade').should(
|
||||
'be.visible'
|
||||
);
|
||||
cy.contains('[data-testid="deposit"]', 'Deposit to trade').click();
|
||||
cy.contains('[data-testid="deposit"]', 'Deposit')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
cy.getByTestId('deposit-submit').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,9 @@ const marketTitle = 'accordion-title';
|
||||
const externalLink = 'external-link';
|
||||
const accordionContent = 'accordion-content';
|
||||
const providerName = 'provider-name';
|
||||
const oracleBannerStatus = 'oracle-banner-status';
|
||||
const oracleBannerDialogTrigger = 'oracle-banner-dialog-trigger';
|
||||
const oracleFullProfile = 'oracle-full-profile';
|
||||
|
||||
describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
@@ -29,7 +32,11 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
|
||||
it('show oracle banner', () => {
|
||||
cy.getByTestId(marketTitle).contains('Oracle').click();
|
||||
cy.getByTestId('oracle-status').should('contain.text', 'COMPROMISED');
|
||||
cy.getByTestId(oracleBannerStatus).should('contain.text', 'COMPROMISED');
|
||||
cy.getByTestId(oracleBannerDialogTrigger)
|
||||
.should('contain.text', 'Show more')
|
||||
.click();
|
||||
cy.getByTestId(oracleFullProfile).should('exist');
|
||||
});
|
||||
|
||||
it('current fees displayed', () => {
|
||||
@@ -192,7 +199,6 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
.within(() => {
|
||||
cy.getByTestId('block-explorer-link').contains('Block explorer');
|
||||
cy.getByTestId('github-link').contains('Oracle repository');
|
||||
cy.getByTestId('verified-accounts').contains('0 proofs of ownership');
|
||||
});
|
||||
cy.getByTestId('dialog-close').click();
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
AuctionTrigger,
|
||||
MarketState,
|
||||
MarketTradingMode,
|
||||
} from '@vegaprotocol/types';
|
||||
|
||||
describe('markets selector', { tags: '@smoke' }, () => {
|
||||
const list = 'market-selector-list';
|
||||
const searchInput = 'search-term';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.window().then((window) => {
|
||||
window.localStorage.setItem('marketId', 'market-1');
|
||||
});
|
||||
cy.mockTradingPage(
|
||||
MarketState.STATE_ACTIVE,
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
|
||||
cy.wait('@Markets');
|
||||
cy.wait('@MarketsData');
|
||||
cy.wait('@MarketsCandles');
|
||||
});
|
||||
|
||||
it('can toggle the sidebar', () => {
|
||||
cy.getByTestId('market-selector').should('be.visible');
|
||||
cy.getByTestId('sidebar-toggle').click();
|
||||
cy.getByTestId('market-selector').should('not.exist');
|
||||
cy.getByTestId('sidebar-toggle').click();
|
||||
cy.getByTestId('market-selector').should('be.visible');
|
||||
});
|
||||
|
||||
// need function keyword as we need 'this' to access market data
|
||||
it('displays data as expected', () => {
|
||||
// TODO: load data from mocks in. Using alias and wrap intermittently fails
|
||||
const data = [
|
||||
{
|
||||
code: 'AAPL.MF21',
|
||||
name: 'Apple Monthly (30 Jun 2022)',
|
||||
markPrice: '46,126.90058',
|
||||
change: '+200.00%',
|
||||
},
|
||||
{
|
||||
code: 'BTCUSD.MF21',
|
||||
name: 'ACTIVE MARKET',
|
||||
markPrice: '46,126.90058',
|
||||
change: '+200.00%',
|
||||
},
|
||||
{
|
||||
code: 'ETHBTC.QM21',
|
||||
name: 'ETHBTC Quarterly (30 Jun 2022)',
|
||||
markPrice: '46,126.90058',
|
||||
change: '+200.00%',
|
||||
},
|
||||
{
|
||||
code: 'SOLUSD',
|
||||
name: 'SUSPENDED MARKET',
|
||||
markPrice: '84.41',
|
||||
change: '+200.00%',
|
||||
},
|
||||
];
|
||||
cy.getByTestId(list)
|
||||
.find('a')
|
||||
.each((item, i) => {
|
||||
const market = data[i];
|
||||
// 6001-MARK-021
|
||||
expect(item.find('h3').text()).equals(market.code);
|
||||
// 6001-MARK-022
|
||||
expect(item.find('h4').text()).equals(market.name);
|
||||
// 6001-MARK-024
|
||||
expect(item.find('[data-testid="market-item-price"]').text()).equals(
|
||||
market.markPrice
|
||||
);
|
||||
// 6001-MARK-023
|
||||
expect(item.find('[data-testid="market-item-change"]').text()).equals(
|
||||
market.change
|
||||
);
|
||||
// 6001-MARK-025
|
||||
expect(item.find('[data-testid="sparkline-svg"]')).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
// 6001-MARK-27
|
||||
it('can use the filter options', () => {
|
||||
// product type
|
||||
cy.getByTestId('product-Spot').click();
|
||||
cy.getByTestId(list).contains('Spot markets coming soon.');
|
||||
cy.getByTestId('product-Perpetual').click();
|
||||
cy.getByTestId(list).contains('Perpetual markets coming soon.');
|
||||
cy.getByTestId('product-Future').click();
|
||||
cy.getByTestId(list).find('a').should('have.length', 4);
|
||||
|
||||
// 6001-MARK-29
|
||||
cy.getByTestId(searchInput).clear().type('btc');
|
||||
cy.getByTestId(list).find('a').should('have.length', 2);
|
||||
cy.getByTestId(list).find('a').eq(0).contains('BTCUSD.MF21');
|
||||
cy.getByTestId(list).find('a').eq(1).contains('ETHBTC.QM21');
|
||||
|
||||
cy.getByTestId(searchInput).clear();
|
||||
cy.getByTestId(list).find('a').should('have.length', 4);
|
||||
});
|
||||
});
|
||||
@@ -31,7 +31,7 @@ describe('Market trading page', () => {
|
||||
// 7002-SORD-001
|
||||
// 7002-SORD-002
|
||||
it('must display market name', () => {
|
||||
cy.getByTestId('popover-trigger').should('not.be.empty');
|
||||
cy.getByTestId('header-title').should('not.be.empty');
|
||||
});
|
||||
|
||||
it('must see market expiry', () => {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { marketsQuery } from '@vegaprotocol/mock';
|
||||
import { getDateTimeFormat } from '@vegaprotocol/utils';
|
||||
|
||||
const dialogCloseBtn = 'dialog-close';
|
||||
const popoverTrigger = 'popover-trigger';
|
||||
|
||||
describe('markets table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
@@ -31,36 +30,6 @@ describe('markets table', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId('sparkline-svg').should('be.visible');
|
||||
});
|
||||
|
||||
it('renders market list drop down', () => {
|
||||
openMarketDropDown();
|
||||
cy.getByTestId('price').invoke('text').should('not.be.empty');
|
||||
cy.getByTestId('trading-mode-col').should('not.be.empty');
|
||||
cy.getByTestId('taker-fee').should('contain.text', '%');
|
||||
cy.getByTestId('market-volume').should('not.be.empty');
|
||||
cy.getByTestId('market-name').should('not.be.empty');
|
||||
|
||||
cy.getByTestId('trading-mode-col')
|
||||
.contains('Monitoring auction - liquidity')
|
||||
.eq(0)
|
||||
.realHover();
|
||||
cy.get('[data-testid="trading-mode-tooltip"] p').should('have.class', '');
|
||||
cy.get(
|
||||
'[data-testid="market-trading-mode"] [data-testid="item-value"]'
|
||||
).realHover();
|
||||
cy.get('[data-testid="trading-mode-tooltip"] p').should(
|
||||
'have.class',
|
||||
'mb-4'
|
||||
);
|
||||
});
|
||||
|
||||
it('able to select market from dropdown', () => {
|
||||
openMarketDropDown();
|
||||
cy.getByTestId('market-link-market-0').first().should('be.visible').click();
|
||||
cy.contains('ACTIVE MARKET').should('be.visible');
|
||||
cy.url().should('include', '/markets/market-0');
|
||||
cy.getByTestId('popover-trigger').should('not.be.empty');
|
||||
});
|
||||
|
||||
it('able to open and sort full market list - market page', () => {
|
||||
const ExpectedSortedMarkets = [
|
||||
'AAPL.MF21',
|
||||
@@ -253,15 +222,3 @@ describe('markets table', { tags: '@smoke' }, () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function openMarketDropDown() {
|
||||
cy.getByTestId(dialogCloseBtn).then((button) => {
|
||||
if (button.is(':visible')) {
|
||||
cy.get('[data-testid^="market-link-"]').should('not.be.empty');
|
||||
cy.getByTestId(dialogCloseBtn).click();
|
||||
}
|
||||
cy.get('[data-testid^="ask-vol-"]').should('be.visible');
|
||||
cy.getByTestId(popoverTrigger).click({ force: true });
|
||||
cy.contains('Loading market data...').should('not.exist');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('accounts', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId('tab-accounts')
|
||||
.get(tradingAccountRowId)
|
||||
.find('[col-id="accounts-actions"]')
|
||||
.should('have.text', ' ');
|
||||
.should('have.text', '');
|
||||
|
||||
cy.getByTestId('tab-accounts')
|
||||
.get(tradingAccountRowId)
|
||||
|
||||
@@ -291,12 +291,58 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
.should('have.text', '200.00');
|
||||
});
|
||||
|
||||
it('must see a pegged order - ask', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_BEST_ASK,
|
||||
offset: '250000',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Ask - 2.50 Peg limit');
|
||||
});
|
||||
|
||||
it('must see a pegged order - bid', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_BEST_BID,
|
||||
offset: '100',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Bid + 0.001 Peg limit');
|
||||
});
|
||||
|
||||
it('must see a pegged order - mid', () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
side: Schema.Side.SIDE_SELL,
|
||||
peggedOrder: {
|
||||
__typename: 'PeggedOrder',
|
||||
reference: Schema.PeggedReference.PEGGED_REFERENCE_MID,
|
||||
offset: '0.5',
|
||||
},
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="type"]')
|
||||
.should('have.text', 'Mid + 0.00001 Peg limit');
|
||||
});
|
||||
|
||||
it('for market typy must not see a price for active or parked orders', () => {
|
||||
// 7003-MORD-005
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
type: Schema.OrderType.TYPE_MARKET,
|
||||
status: Schema.OrderStatus.STATUS_PARKED,
|
||||
peggedOrder: null,
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
.find('[col-id="price"]')
|
||||
@@ -337,6 +383,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
|
||||
updateOrder({
|
||||
id: orderId,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
peggedOrder: {},
|
||||
liquidityProvisionId: '6536',
|
||||
});
|
||||
cy.get(`[row-id=${orderId}]`)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import type { CyHttpMessages } from 'cypress/types/net-stubbing';
|
||||
import type { Provider, Status } from '@vegaprotocol/oracles';
|
||||
import type { Provider, Status } from '@vegaprotocol/markets';
|
||||
import {
|
||||
accountsQuery,
|
||||
assetQuery,
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
blockStatisticsQuery,
|
||||
} from '@vegaprotocol/mock';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/market-list';
|
||||
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/markets';
|
||||
|
||||
type MarketPageMockData = {
|
||||
state: Schema.MarketState;
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
function ReactMarkdown({ children }) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default ReactMarkdown;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { marketsWithDataProvider } from '@vegaprotocol/market-list';
|
||||
import { marketsWithDataProvider } from '@vegaprotocol/markets';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
@@ -5,10 +5,9 @@ import {
|
||||
lpAggregatedDataProvider,
|
||||
useCheckLiquidityStatus,
|
||||
} from '@vegaprotocol/liquidity';
|
||||
import { tooltipMapping } from '@vegaprotocol/market-info';
|
||||
import { tooltipMapping } from '@vegaprotocol/markets';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
createDocsLinks,
|
||||
formatNumberPercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -38,8 +37,8 @@ import type { LiquidityProvisionData, Filter } from '@vegaprotocol/liquidity';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
import { useMarket, useStaticMarketData } from '@vegaprotocol/market-list';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useMarket, useStaticMarketData } from '@vegaprotocol/markets';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
|
||||
const enum LiquidityTabs {
|
||||
Active = 'active',
|
||||
@@ -147,7 +146,6 @@ const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
|
||||
const symbol =
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
const { VEGA_DOCS_URL } = useEnvironment();
|
||||
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.market_liquidity_stakeToCcyVolume,
|
||||
@@ -216,10 +214,12 @@ const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
|
||||
<div className="break-word">{marketId}</div>
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Learn more')}>
|
||||
{VEGA_DOCS_URL && (
|
||||
<ExternalLink href={createDocsLinks(VEGA_DOCS_URL).LIQUIDITY}>
|
||||
{DocsLinks ? (
|
||||
<ExternalLink href={DocsLinks.LIQUIDITY}>
|
||||
{t('Providing liquidity')}
|
||||
</ExternalLink>
|
||||
) : (
|
||||
(null as React.ReactNode)
|
||||
)}
|
||||
</HeaderStat>
|
||||
</Header>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { AssetDropdown } from './asset-dropdown';
|
||||
|
||||
const createAssets = (count = 3) => {
|
||||
return new Array(count).fill(null).map((_, i) => ({
|
||||
id: i.toString(),
|
||||
symbol: 'asset-1',
|
||||
}));
|
||||
};
|
||||
|
||||
describe('AssetDropdown', () => {
|
||||
const assets = createAssets();
|
||||
|
||||
it('renders and selects chosen assets', async () => {
|
||||
const mockOnSelect = jest.fn();
|
||||
render(
|
||||
<AssetDropdown
|
||||
checkedAssets={[]}
|
||||
assets={assets}
|
||||
onSelect={mockOnSelect}
|
||||
onReset={jest.fn()}
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
const items = screen.getAllByRole('menuitemcheckbox');
|
||||
expect(items).toHaveLength(assets.length);
|
||||
expect(items.map((i) => i.textContent)).toEqual(
|
||||
assets.map((a) => a.symbol)
|
||||
);
|
||||
await userEvent.click(items[0]);
|
||||
expect(mockOnSelect).toHaveBeenCalledWith(assets[0].id, true);
|
||||
});
|
||||
|
||||
it('unselects already selected assets', async () => {
|
||||
const mockOnSelect = jest.fn();
|
||||
render(
|
||||
<AssetDropdown
|
||||
checkedAssets={[assets[0].id]}
|
||||
assets={assets}
|
||||
onSelect={mockOnSelect}
|
||||
onReset={jest.fn()}
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
const items = screen.getAllByRole('menuitemcheckbox');
|
||||
await userEvent.click(items[0]);
|
||||
expect(mockOnSelect).toHaveBeenCalledWith(assets[0].id, false);
|
||||
});
|
||||
|
||||
it('can be reset clearing all assets', async () => {
|
||||
const mockOnSelect = jest.fn();
|
||||
const mockOnReset = jest.fn();
|
||||
render(
|
||||
<AssetDropdown
|
||||
checkedAssets={assets.map((a) => a.id)}
|
||||
assets={assets}
|
||||
onSelect={mockOnSelect}
|
||||
onReset={mockOnReset}
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
const items = screen.getAllByRole('menuitemcheckbox');
|
||||
// all should be checked
|
||||
items.forEach((item) => {
|
||||
expect(item).toBeChecked();
|
||||
});
|
||||
await userEvent.click(screen.getByText('Reset'));
|
||||
expect(mockOnReset).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('doesnt render if no assets provided', async () => {
|
||||
const { container } = render(
|
||||
<AssetDropdown
|
||||
checkedAssets={[]}
|
||||
assets={[]}
|
||||
onSelect={jest.fn()}
|
||||
onReset={jest.fn()}
|
||||
/>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const AssetDropdown = ({
|
||||
assets,
|
||||
checkedAssets,
|
||||
onSelect,
|
||||
onReset,
|
||||
}: {
|
||||
assets: Array<{ id: string; symbol: string }> | undefined;
|
||||
checkedAssets: string[];
|
||||
onSelect: (id: string, checked: boolean) => void;
|
||||
onReset: () => void;
|
||||
}) => {
|
||||
if (!assets?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<DropdownMenuTrigger data-testid="asset-trigger">
|
||||
<span className="px-1">$</span>
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={onReset}>{t('Reset')}</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
{assets?.map((a) => {
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={a.id}
|
||||
checked={checkedAssets.includes(a.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (typeof checked === 'boolean') {
|
||||
onSelect(a.id, checked);
|
||||
}
|
||||
}}
|
||||
data-testid={`asset-id-${a.id}`}
|
||||
>
|
||||
{a.symbol}
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuCheckboxItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
+76
-94
@@ -2,119 +2,101 @@ import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketProposalNotification } from '@vegaprotocol/proposals';
|
||||
import type { Market } from '@vegaprotocol/market-list';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { getExpiryDate, getMarketExpiryDate } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
ColumnKind,
|
||||
SelectMarketPopover,
|
||||
} from '../../components/select-market';
|
||||
import type { OnCellClickHandler } from '../../components/select-market';
|
||||
import { Header, HeaderStat } from '../../components/header';
|
||||
import { NO_MARKET } from './constants';
|
||||
import { HeaderStat } from '../../components/header';
|
||||
import { MarketMarkPrice } from '../../components/market-mark-price';
|
||||
import { Last24hPriceChange, Last24hVolume } from '@vegaprotocol/market-info';
|
||||
import { Last24hPriceChange, Last24hVolume } from '@vegaprotocol/markets';
|
||||
import { MarketState } from '../../components/market-state';
|
||||
import { HeaderStatMarketTradingMode } from '../../components/market-trading-mode';
|
||||
import { MarketLiquiditySupplied } from '../../components/liquidity-supplied';
|
||||
import { MarketState as State } from '@vegaprotocol/types';
|
||||
|
||||
interface TradeMarketHeaderProps {
|
||||
interface HeaderStatsProps {
|
||||
market: Market | null;
|
||||
onSelect: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
export const TradeMarketHeader = ({
|
||||
market,
|
||||
onSelect,
|
||||
}: TradeMarketHeaderProps) => {
|
||||
export const HeaderStats = ({ market }: HeaderStatsProps) => {
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
|
||||
const asset = market?.tradableInstrument.instrument.product?.settlementAsset;
|
||||
|
||||
const onCellClick: OnCellClickHandler = (e, kind, value) => {
|
||||
if (value && kind === ColumnKind.Asset) {
|
||||
openAssetDetailsDialog(value, e.target as HTMLElement);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Header
|
||||
title={
|
||||
<SelectMarketPopover
|
||||
marketCode={market?.tradableInstrument.instrument.code || NO_MARKET}
|
||||
marketName={market?.tradableInstrument.instrument.name || NO_MARKET}
|
||||
onSelect={onSelect}
|
||||
onCellClick={onCellClick}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HeaderStat
|
||||
heading={t('Expiry')}
|
||||
description={
|
||||
market && (
|
||||
<ExpiryTooltipContent
|
||||
market={market}
|
||||
explorerUrl={VEGA_EXPLORER_URL}
|
||||
/>
|
||||
)
|
||||
}
|
||||
testId="market-expiry"
|
||||
>
|
||||
<ExpiryLabel market={market} />
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Price')} testId="market-price">
|
||||
<MarketMarkPrice
|
||||
marketId={market?.id}
|
||||
decimalPlaces={market?.decimalPlaces}
|
||||
/>
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Change (24h)')} testId="market-change">
|
||||
<Last24hPriceChange
|
||||
marketId={market?.id}
|
||||
decimalPlaces={market?.decimalPlaces}
|
||||
/>
|
||||
</HeaderStat>
|
||||
<HeaderStat
|
||||
heading={t('Volume (24h)')}
|
||||
testId="market-volume"
|
||||
description={t(
|
||||
'The total number of contracts traded in the last 24 hours.'
|
||||
)}
|
||||
>
|
||||
<Last24hVolume
|
||||
marketId={market?.id}
|
||||
positionDecimalPlaces={market?.positionDecimalPlaces}
|
||||
/>
|
||||
</HeaderStat>
|
||||
<HeaderStatMarketTradingMode
|
||||
marketId={market?.id}
|
||||
initialTradingMode={market?.tradingMode}
|
||||
/>
|
||||
<MarketState market={market} />
|
||||
{asset ? (
|
||||
<HeaderStat
|
||||
heading={t('Settlement asset')}
|
||||
testId="market-settlement-asset"
|
||||
<div className="flex flex-col justify-end lg:pt-4">
|
||||
<div className="xl:flex xl:gap-4 items-end">
|
||||
<div
|
||||
data-testid="header-summary"
|
||||
className="flex flex-nowrap items-end xl:flex-1 w-full overflow-x-auto text-xs"
|
||||
>
|
||||
<div>
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(asset.id, e.target as HTMLElement);
|
||||
}}
|
||||
<HeaderStat
|
||||
heading={t('Expiry')}
|
||||
description={
|
||||
market && (
|
||||
<ExpiryTooltipContent
|
||||
market={market}
|
||||
explorerUrl={VEGA_EXPLORER_URL}
|
||||
/>
|
||||
)
|
||||
}
|
||||
testId="market-expiry"
|
||||
>
|
||||
<ExpiryLabel market={market} />
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Price')} testId="market-price">
|
||||
<MarketMarkPrice
|
||||
marketId={market?.id}
|
||||
decimalPlaces={market?.decimalPlaces}
|
||||
/>
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Change (24h)')} testId="market-change">
|
||||
<Last24hPriceChange
|
||||
marketId={market?.id}
|
||||
decimalPlaces={market?.decimalPlaces}
|
||||
/>
|
||||
</HeaderStat>
|
||||
<HeaderStat
|
||||
heading={t('Volume (24h)')}
|
||||
testId="market-volume"
|
||||
description={t(
|
||||
'The total number of contracts traded in the last 24 hours.'
|
||||
)}
|
||||
>
|
||||
<Last24hVolume
|
||||
marketId={market?.id}
|
||||
positionDecimalPlaces={market?.positionDecimalPlaces}
|
||||
/>
|
||||
</HeaderStat>
|
||||
<HeaderStatMarketTradingMode
|
||||
marketId={market?.id}
|
||||
initialTradingMode={market?.tradingMode}
|
||||
/>
|
||||
<MarketState market={market} />
|
||||
{asset ? (
|
||||
<HeaderStat
|
||||
heading={t('Settlement asset')}
|
||||
testId="market-settlement-asset"
|
||||
>
|
||||
{asset.symbol}
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</HeaderStat>
|
||||
) : null}
|
||||
<MarketLiquiditySupplied
|
||||
marketId={market?.id}
|
||||
assetDecimals={asset?.decimals || 0}
|
||||
/>
|
||||
<MarketProposalNotification marketId={market?.id} />
|
||||
</Header>
|
||||
<div>
|
||||
<ButtonLink
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(asset.id, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
{asset.symbol}
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</HeaderStat>
|
||||
) : null}
|
||||
<MarketLiquiditySupplied
|
||||
marketId={market?.id}
|
||||
assetDecimals={asset?.decimals || 0}
|
||||
/>
|
||||
<MarketProposalNotification marketId={market?.id} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { createMarketFragment } from '@vegaprotocol/mock';
|
||||
import { MarketSelectorItem } from './market-selector-item';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type {
|
||||
MarketDataUpdateFieldsFragment,
|
||||
MarketDataUpdateSubscription,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketDataUpdateDocument } from '@vegaprotocol/markets';
|
||||
import {
|
||||
AuctionTrigger,
|
||||
MarketState,
|
||||
MarketTradingMode,
|
||||
} from '@vegaprotocol/types';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
|
||||
describe('MarketSelectorItem', () => {
|
||||
const market = createMarketFragment({
|
||||
id: 'market-0',
|
||||
decimalPlaces: 2,
|
||||
// @ts-ignore fragment doesn't contain candles
|
||||
candles: [{ close: '5' }, { close: '10' }],
|
||||
});
|
||||
const marketData: MarketDataUpdateFieldsFragment = {
|
||||
__typename: 'ObservableMarketData',
|
||||
marketId: market.id,
|
||||
auctionEnd: null,
|
||||
auctionStart: null,
|
||||
bestBidPrice: '100',
|
||||
bestBidVolume: '100',
|
||||
bestOfferPrice: '100',
|
||||
bestOfferVolume: '100',
|
||||
bestStaticBidPrice: '100',
|
||||
bestStaticBidVolume: '100',
|
||||
bestStaticOfferPrice: '100',
|
||||
bestStaticOfferVolume: '100',
|
||||
indicativePrice: '100',
|
||||
indicativeVolume: '100',
|
||||
marketState: MarketState.STATE_ACTIVE,
|
||||
marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
marketValueProxy: '100',
|
||||
markPrice: '50000',
|
||||
midPrice: '100',
|
||||
staticMidPrice: '100',
|
||||
openInterest: '100',
|
||||
suppliedStake: '1000000',
|
||||
targetStake: '1000000',
|
||||
trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
|
||||
priceMonitoringBounds: null,
|
||||
};
|
||||
const mock: MockedResponse<MarketDataUpdateSubscription> = {
|
||||
request: {
|
||||
query: MarketDataUpdateDocument,
|
||||
variables: {
|
||||
marketId: market.id,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsData: [marketData],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockOnSelect = jest.fn();
|
||||
|
||||
const renderJsx = () => {
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<MarketSelectorItem
|
||||
market={market}
|
||||
currentMarketId={market.id}
|
||||
style={{}}
|
||||
onSelect={mockOnSelect}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders market information', async () => {
|
||||
renderJsx();
|
||||
|
||||
const link = screen.getByRole('link');
|
||||
// link renders and is styled
|
||||
expect(link).toHaveAttribute('href', '/markets/' + market.id);
|
||||
|
||||
expect(link).toHaveClass('ring-1');
|
||||
|
||||
expect(screen.getByTestId('market-item-price')).toHaveTextContent('-');
|
||||
|
||||
// candles are loaded immediately
|
||||
expect(screen.getByTestId('market-item-change')).toHaveTextContent(
|
||||
'+100.00%'
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('market-item-price')).toHaveTextContent(
|
||||
addDecimalsFormatNumber(marketData.markPrice, market.decimalPlaces)
|
||||
);
|
||||
});
|
||||
|
||||
await userEvent.click(link);
|
||||
|
||||
expect(mockOnSelect).toHaveBeenCalledWith(market.id);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumber,
|
||||
priceChangePercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
|
||||
import { useMarketDataUpdateSubscription } from '@vegaprotocol/markets';
|
||||
import { Sparkline } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const MarketSelectorItem = ({
|
||||
market,
|
||||
style,
|
||||
currentMarketId,
|
||||
onSelect,
|
||||
}: {
|
||||
market: MarketMaybeWithDataAndCandles;
|
||||
style: CSSProperties;
|
||||
currentMarketId?: string;
|
||||
onSelect?: (marketId: string) => void;
|
||||
}) => {
|
||||
const wrapperClasses = classNames(
|
||||
'block bg-vega-light-100 dark:bg-vega-dark-100 rounded-lg p-4',
|
||||
'min-h-[120px]',
|
||||
{
|
||||
'ring-1 ring-vega-light-300 dark:ring-vega-dark-300':
|
||||
currentMarketId === market.id,
|
||||
}
|
||||
);
|
||||
return (
|
||||
<div style={style} className="my-0.5 px-4">
|
||||
<Link
|
||||
to={`/markets/${market.id}`}
|
||||
className={wrapperClasses}
|
||||
onClick={() => {
|
||||
onSelect && onSelect(market.id);
|
||||
}}
|
||||
>
|
||||
<h3>{market.tradableInstrument.instrument.code}</h3>
|
||||
<h4
|
||||
title={market.tradableInstrument.instrument.name}
|
||||
className="text-sm text-vega-light-300 dark:text-vega-dark-300 text-ellipsis whitespace-nowrap overflow-hidden"
|
||||
>
|
||||
{market.tradableInstrument.instrument.name}
|
||||
</h4>
|
||||
<MarketData market={market} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
const { data } = useMarketDataUpdateSubscription({
|
||||
variables: {
|
||||
marketId: market.id,
|
||||
},
|
||||
});
|
||||
|
||||
const marketData = data?.marketsData[0];
|
||||
|
||||
// use market data price if available as this is comes from
|
||||
// the subscription
|
||||
const price = marketData
|
||||
? addDecimalsFormatNumber(marketData.markPrice, market.decimalPlaces)
|
||||
: market.data
|
||||
? addDecimalsFormatNumber(market.data.markPrice, market.decimalPlaces)
|
||||
: '-';
|
||||
|
||||
return (
|
||||
<div className="flex flex-nowrap justify-between items-center mt-1">
|
||||
<div className="w-1/2">
|
||||
<div
|
||||
className="text-ellipsis whitespace-nowrap overflow-hidden"
|
||||
data-testid="market-item-price"
|
||||
>
|
||||
{price}
|
||||
</div>
|
||||
{market.candles && (
|
||||
<PriceChange candles={market.candles.map((c) => c.close)} />
|
||||
)}
|
||||
</div>
|
||||
<div className="w-1/2 max-w-[120px]">
|
||||
{market.candles ? (
|
||||
<Sparkline
|
||||
width={120}
|
||||
height={20}
|
||||
data={market.candles.filter(Boolean).map((c) => Number(c.close))}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PriceChange = ({ candles }: { candles: string[] }) => {
|
||||
const priceChange = candles ? priceChangePercentage(candles) : undefined;
|
||||
const priceChangeClasses = classNames('text-xs', {
|
||||
'text-vega-pink': priceChange && priceChange < 0,
|
||||
'text-vega-green': priceChange && priceChange > 0,
|
||||
});
|
||||
let prefix = '';
|
||||
if (priceChange && priceChange > 0) {
|
||||
prefix = '+';
|
||||
}
|
||||
const formattedChange = formatNumber(Number(priceChange), 2);
|
||||
return (
|
||||
<div className={priceChangeClasses} data-testid="market-item-change">
|
||||
{priceChange ? `${prefix}${formattedChange}%` : '-'}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,288 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { MarketSelector } from './market-selector';
|
||||
import { useMarketList } from '@vegaprotocol/markets';
|
||||
import { createMarketFragment } from '@vegaprotocol/mock';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { SortType } from './sort-dropdown';
|
||||
import { SortTypeMapping } from './sort-dropdown';
|
||||
import { Sort } from './sort-dropdown';
|
||||
import { subDays } from 'date-fns';
|
||||
import { isMarketActive } from './use-market-selector-list';
|
||||
|
||||
jest.mock('@vegaprotocol/markets');
|
||||
const mockUseMarketList = useMarketList as jest.Mock;
|
||||
|
||||
// mock market list items to avoid subscriptions starting
|
||||
jest.mock('./market-selector-item', () => ({
|
||||
MarketSelectorItem: (props: { market: { id: string } }) => (
|
||||
<div data-testid={props.market.id} />
|
||||
),
|
||||
}));
|
||||
|
||||
// without a real DOM autosize won't render with an actual height or width
|
||||
jest.mock('react-virtualized-auto-sizer', () => {
|
||||
// eslint-disable-next-line react/display-name
|
||||
return ({
|
||||
children,
|
||||
}: {
|
||||
children: (size: { width: number; height: number }) => ReactNode;
|
||||
}) => <div>{children({ width: 300, height: 1000 })}</div>;
|
||||
});
|
||||
|
||||
describe('MarketSelector', () => {
|
||||
const markets = [
|
||||
createMarketFragment({
|
||||
id: 'market-0',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
code: 'a',
|
||||
name: 'a',
|
||||
product: {
|
||||
settlementAsset: {
|
||||
id: 'asset-0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// @ts-ignore candles get joined outside this type
|
||||
candles: [{ close: '100' }, { close: '200' }],
|
||||
marketTimestamps: {
|
||||
open: subDays(new Date(), 1).toISOString(),
|
||||
},
|
||||
}),
|
||||
createMarketFragment({
|
||||
id: 'market-1',
|
||||
state: MarketState.STATE_SUSPENDED,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
code: 'b',
|
||||
name: 'b',
|
||||
product: {
|
||||
settlementAsset: {
|
||||
id: 'asset-0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// @ts-ignore candles get joined outside this type
|
||||
candles: [{ close: '100' }, { close: '400' }],
|
||||
marketTimestamps: {
|
||||
open: subDays(new Date(), 2).toISOString(),
|
||||
},
|
||||
}),
|
||||
createMarketFragment({
|
||||
id: 'market-2',
|
||||
state: MarketState.STATE_CLOSED,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
settlementAsset: {
|
||||
id: 'asset-1',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
createMarketFragment({
|
||||
id: 'market-3',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
code: 'c',
|
||||
name: 'c',
|
||||
product: {
|
||||
settlementAsset: {
|
||||
id: 'asset-1',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// @ts-ignore candles get joined outside this type
|
||||
candles: [{ close: '100' }, { close: '1000' }],
|
||||
marketTimestamps: {
|
||||
open: subDays(new Date(), 0).toISOString(),
|
||||
},
|
||||
}),
|
||||
createMarketFragment({
|
||||
id: 'market-4',
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
code: 'cd',
|
||||
name: 'cd',
|
||||
product: {
|
||||
settlementAsset: {
|
||||
id: 'asset-2',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// @ts-ignore candles get joined outside this type
|
||||
candles: [{ close: '100' }, { close: '300' }],
|
||||
marketTimestamps: {
|
||||
open: subDays(new Date(), 3).toISOString(),
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
const activeMarkets = markets.filter((m) => isMarketActive(m.state));
|
||||
mockUseMarketList.mockReturnValue({
|
||||
data: markets,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
it('renders only active markets', () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(
|
||||
activeMarkets.length
|
||||
);
|
||||
expect(screen.getByRole('link')).toHaveTextContent('All markets');
|
||||
});
|
||||
|
||||
it('filters by product type', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId('product-Spot'));
|
||||
expect(screen.queryAllByTestId(/market-\d/)).toHaveLength(0);
|
||||
expect(screen.getByTestId('no-items')).toHaveTextContent(
|
||||
'Spot markets coming soon.'
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId('product-Perpetual'));
|
||||
expect(screen.queryAllByTestId(/market-\d/)).toHaveLength(0);
|
||||
expect(screen.getByTestId('no-items')).toHaveTextContent(
|
||||
'Perpetual markets coming soon.'
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId('product-Future'));
|
||||
expect(screen.queryAllByTestId(/market-\d/)).toHaveLength(
|
||||
activeMarkets.length
|
||||
);
|
||||
expect(screen.queryByTestId('no-items')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('filters by search term', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await userEvent.type(screen.getByTestId('search-term'), 'zzz');
|
||||
expect(screen.getByTestId('no-items')).toHaveTextContent('No markets');
|
||||
const input = screen.getByTestId('search-term');
|
||||
await userEvent.clear(input);
|
||||
await userEvent.type(input, 'a');
|
||||
expect(input).toHaveValue('a');
|
||||
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(1);
|
||||
expect(screen.getByTestId('market-0')).toBeInTheDocument();
|
||||
|
||||
await userEvent.clear(input);
|
||||
await userEvent.type(input, 'b');
|
||||
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(1);
|
||||
expect(screen.getByTestId('market-1')).toBeInTheDocument();
|
||||
|
||||
await userEvent.clear(input);
|
||||
await userEvent.type(input, 'c');
|
||||
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(2);
|
||||
expect(screen.getByTestId('market-3')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('market-4')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('filters by asset', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId('asset-trigger'));
|
||||
expect(screen.getAllByTestId(/asset-id/)).toHaveLength(3);
|
||||
await userEvent.click(screen.getByTestId('asset-id-asset-0'));
|
||||
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(2);
|
||||
expect(screen.getByTestId('market-0')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('market-1')).toBeInTheDocument();
|
||||
|
||||
// reopen asset dropdown and add asset-1
|
||||
await userEvent.click(screen.getByTestId('asset-trigger'));
|
||||
await userEvent.click(screen.getByTestId('asset-id-asset-1'));
|
||||
|
||||
// all markets with asset-0 or asset-1 shown (no market id as market is closed)
|
||||
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(3);
|
||||
expect(screen.getByTestId('market-0')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('market-1')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('market-3')).toBeInTheDocument();
|
||||
|
||||
// reopen and uncheck asset-0
|
||||
await userEvent.click(screen.getByTestId('asset-trigger'));
|
||||
await userEvent.click(screen.getByTestId('asset-id-asset-0'));
|
||||
|
||||
expect(screen.getAllByTestId(/market-\d/)).toHaveLength(1);
|
||||
expect(screen.getByTestId('market-3')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('sorts by gained', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId('sort-trigger'));
|
||||
const options = screen.getAllByTestId(/sort-item/);
|
||||
expect(options.map((o) => o.textContent)).toEqual(
|
||||
Object.entries(Sort)
|
||||
.filter(([key]) => key !== Sort.None)
|
||||
.map(([key]) => SortTypeMapping[key as SortType])
|
||||
);
|
||||
await userEvent.click(screen.getByTestId('sort-item-Gained'));
|
||||
expect(
|
||||
screen
|
||||
.getAllByTestId(/market-\d/)
|
||||
.map((el) => el.getAttribute('data-testid'))
|
||||
).toEqual([markets[3].id, markets[1].id, markets[4].id, markets[0].id]);
|
||||
});
|
||||
|
||||
it('sorts by lost', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId('sort-trigger'));
|
||||
await userEvent.click(screen.getByTestId('sort-item-Lost'));
|
||||
expect(
|
||||
screen
|
||||
.getAllByTestId(/market-\d/)
|
||||
.map((el) => el.getAttribute('data-testid'))
|
||||
).toEqual([markets[0].id, markets[4].id, markets[1].id, markets[3].id]);
|
||||
});
|
||||
|
||||
it('sorts by new', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MarketSelector currentMarketId="market-0" />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId('sort-trigger'));
|
||||
await userEvent.click(screen.getByTestId('sort-item-New'));
|
||||
expect(
|
||||
screen
|
||||
.getAllByTestId(/market-\d/)
|
||||
.map((el) => el.getAttribute('data-testid'))
|
||||
).toEqual([markets[3].id, markets[0].id, markets[1].id, markets[4].id]);
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user