Compare commits

..
Author SHA1 Message Date
Matthew Russell 84e82321b8 wip: fix me - move to ui toolkit 2023-02-19 00:50:09 -08:00
Matthew Russell 5e3cfbe8b4 feat: make composable navigation 2023-02-19 00:37:50 -08:00
mattrussell36 7de333e1ea chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-17 18:05:59 +00:00
Sam Keen 2123afc4b0 fix(governance): overstaked penalty shown as NaN (#2932) 2023-02-17 16:42:22 +00:00
Sam Keen ea91fa1723 feat(governance): ranking score for all validator types (#2929) 2023-02-17 16:41:56 +00:00
Sam Keen 4dcdec7a35 fix(governance): navigation token drop down behaviour (#2931) 2023-02-17 16:41:32 +00:00
Joe Tsang 196f1914a3 test(governance): check proposals on homepage (#2908) 2023-02-17 15:17:04 +00:00
daro-maj ea4c7a4a37 test(trading): fixed dropdown issue in markets tests (#2930) 2023-02-17 15:31:14 +01:00
m.ray c22fec97b4 feat(trading): deposit to trade collateral tab pinned row (#2921) 2023-02-17 13:32:20 +00:00
mattrussell36 989a0456c0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-17 12:09:22 +00:00
daro-maj 8c79a94a8c test(trading): added waiting for market (#2927) 2023-02-17 06:57:19 -05:00
mattrussell36 a7a6a820c0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-17 06:08:47 +00:00
mattrussell36 1fa09a690e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-17 00:13:12 +00:00
mattrussell36 b0a84970f2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-16 18:06:17 +00:00
Maciek b2a115f935 chore(trading): handle timeout and offline errors (#2918) 2023-02-16 17:30:32 +01:00
mattrussell36 9dfed0a211 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-16 12:09:15 +00:00
mattrussell36 5283676be7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-16 06:07:52 +00:00
Matthew Russell a82509f0e0 feat(trading): datanode block header for env and node switcher (#2905) 2023-02-15 19:52:54 -08:00
mattrussell36 64c92ce91d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-16 00:10:37 +00:00
52 changed files with 1410 additions and 276 deletions
+100 -33
View File
@@ -2761,7 +2761,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "86666.297",
"total_removed": "0",
"locked_amount": "69391.0872001880742983107",
"locked_amount": "68916.5304132697229014062",
"deposits": [
{
"amount": "86666.297",
@@ -2827,7 +2827,7 @@
"tranche_end": "2023-06-01T00:00:00.000Z",
"total_added": "2500",
"total_removed": "0",
"locked_amount": "1445.66592261904775",
"locked_amount": "1418.21231430606425",
"deposits": [
{
"amount": "2500",
@@ -3215,7 +3215,7 @@
"tranche_end": "2023-08-01T00:00:00.000Z",
"total_added": "37500",
"total_removed": "2836.415649675",
"locked_amount": "34442.91695441989125",
"locked_amount": "34028.837668815225",
"deposits": [
{
"amount": "7500",
@@ -3326,7 +3326,7 @@
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "129999.45",
"total_removed": "0",
"locked_amount": "69327.776564239291943985",
"locked_amount": "68853.652750677974505795",
"deposits": [
{
"amount": "129999.45",
@@ -3392,7 +3392,7 @@
"tranche_end": "2023-09-03T00:00:00.000Z",
"total_added": "62600",
"total_removed": "0",
"locked_amount": "34171.79286529680654",
"locked_amount": "33829.0153855910767",
"deposits": [
{
"amount": "10000",
@@ -3585,7 +3585,7 @@
"tranche_end": "2023-09-17T00:00:00.000Z",
"total_added": "5000",
"total_removed": "0",
"locked_amount": "2921.1572488584475",
"locked_amount": "2893.778855910705",
"deposits": [
{
"amount": "5000",
@@ -3796,7 +3796,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "97499.58",
"total_removed": "0",
"locked_amount": "11226.2922579206452066194",
"locked_amount": "10761.2209634480270840046",
"deposits": [
{
"amount": "97499.58",
@@ -3829,7 +3829,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "135173.4239508",
"total_removed": "98230.390980249184455396",
"locked_amount": "15344.40337807380869704113192",
"locked_amount": "14708.73120973917718086298914",
"deposits": [
{
"amount": "135173.4239508",
@@ -3875,7 +3875,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "32499.86",
"total_removed": "0",
"locked_amount": "4722.707285209589580316",
"locked_amount": "4527.0598229766283023892",
"deposits": [
{
"amount": "32499.86",
@@ -3908,7 +3908,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "10833.29",
"total_removed": "0",
"locked_amount": "1537.1953932689953826786",
"locked_amount": "1473.514042830201860202",
"deposits": [
{
"amount": "10833.29",
@@ -3941,7 +3941,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "22749.93",
"total_removed": "4720.860935375",
"locked_amount": "5746.3797064299737259078",
"locked_amount": "5508.324595517016566601",
"deposits": [
{
"amount": "6500",
@@ -4093,7 +4093,7 @@
"tranche_end": "2023-05-01T00:00:00.000Z",
"total_added": "22500",
"total_removed": "4373.451408",
"locked_amount": "9229.28608425414375",
"locked_amount": "8980.8385128913431",
"deposits": [
{
"amount": "7500",
@@ -4324,7 +4324,7 @@
"tranche_end": "2023-06-02T00:00:00.000Z",
"total_added": "1939928.38",
"total_removed": "928642.9598472029154",
"locked_amount": "564675.8365855764677193232",
"locked_amount": "554053.412289953077578596",
"deposits": [
{
"amount": "1852091.69",
@@ -7298,10 +7298,15 @@
"tranche_id": 10,
"tranche_start": "2021-07-15T23:37:11.000Z",
"tranche_end": "2021-07-15T23:37:11.000Z",
"total_added": "6159302.299000000000000001",
"total_removed": "6113483.280000000000000001",
"total_added": "6259302.299000000000000001",
"total_removed": "6213483.280000000000000001",
"locked_amount": "0",
"deposits": [
{
"amount": "100000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"tx": "0xa4417547c32fb4936e0dd6ee338ba31b2fd622388fe8a3906d9e7020d5e8371b"
},
{
"amount": "100000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
@@ -7824,6 +7829,11 @@
}
],
"withdrawals": [
{
"amount": "100000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"tx": "0xa4417547c32fb4936e0dd6ee338ba31b2fd622388fe8a3906d9e7020d5e8371b"
},
{
"amount": "100000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
@@ -8289,6 +8299,12 @@
{
"address": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"deposits": [
{
"amount": "100000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"tranche_id": 10,
"tx": "0xa4417547c32fb4936e0dd6ee338ba31b2fd622388fe8a3906d9e7020d5e8371b"
},
{
"amount": "100000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
@@ -8471,6 +8487,12 @@
}
],
"withdrawals": [
{
"amount": "100000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
"tranche_id": 10,
"tx": "0xa4417547c32fb4936e0dd6ee338ba31b2fd622388fe8a3906d9e7020d5e8371b"
},
{
"amount": "100000",
"user": "0xb2d6DEC77558Cf8EdB7c428d23E70Eab0688544f",
@@ -8646,8 +8668,8 @@
"tx": "0xac16a4ce688d40a482a59914d68c3a676592f8804ee8f0781b66a4ba5ccfbdfc"
}
],
"total_tokens": "2956651",
"withdrawn_tokens": "2956651",
"total_tokens": "3056651",
"withdrawn_tokens": "3056651",
"remaining_tokens": "0"
},
{
@@ -36594,7 +36616,7 @@
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "3732368.4671",
"total_removed": "592998.0503546212334",
"locked_amount": "892211.486316038327909139488",
"locked_amount": "875888.518509001519437488391",
"deposits": [
{
"amount": "1998.95815",
@@ -37919,8 +37941,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-12-05T00:00:00.000Z",
"total_added": "15870102.715470999700000001",
"total_removed": "565985.90378116129377952",
"locked_amount": "8463412.2306648275892962453531986551946473",
"total_removed": "570447.40018226362274952",
"locked_amount": "8405532.0348557766289364422878397935063531",
"deposits": [
{
"amount": "16249.93",
@@ -38449,6 +38471,16 @@
"user": "0x20CD77B9FC2f1fEDfb6F184E25f7127BFE991C8b",
"tx": "0x307e498402679f0f8ee11dcd3dfc684db5558203469f07ce58e98a4f87f0fa62"
},
{
"amount": "1640.93679110232897",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tx": "0x4870598253a2a664d9c96af37904f08ac772ee9620b5975560ef7e7fb51c9a7f"
},
{
"amount": "2820.55961",
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
"tx": "0x039c7d1eb6b18a13c3fb91589403d43710e12b319e7d40936280a576502f80fe"
},
{
"amount": "856.08784586478614",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
@@ -41521,6 +41553,12 @@
}
],
"withdrawals": [
{
"amount": "1640.93679110232897",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
"tranche_id": 2,
"tx": "0x4870598253a2a664d9c96af37904f08ac772ee9620b5975560ef7e7fb51c9a7f"
},
{
"amount": "856.08784586478614",
"user": "0xe3eB4CF43C072658401996b71D43eE26E573562D",
@@ -41751,8 +41789,8 @@
}
],
"total_tokens": "150551.801",
"withdrawn_tokens": "68648.27553278775093",
"remaining_tokens": "81903.52546721224907"
"withdrawn_tokens": "70289.2123238900799",
"remaining_tokens": "80262.5886761099201"
},
{
"address": "0x4d982Ab0823fD2f48e934a7be2bb0a5374a26148",
@@ -41954,6 +41992,12 @@
}
],
"withdrawals": [
{
"amount": "2820.55961",
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
"tranche_id": 2,
"tx": "0x039c7d1eb6b18a13c3fb91589403d43710e12b319e7d40936280a576502f80fe"
},
{
"amount": "1412.763584",
"user": "0xF4c75FdbAe821C6B5DBB9f001bB23cBA58D1bA37",
@@ -42154,8 +42198,8 @@
}
],
"total_tokens": "200000",
"withdrawn_tokens": "90814.016456",
"remaining_tokens": "109185.983544"
"withdrawn_tokens": "93634.576066",
"remaining_tokens": "106365.423934"
},
{
"address": "0x1b956E6c00E238194B331eddEFF72Cb5f28A8d01",
@@ -43508,7 +43552,7 @@
"tranche_end": "2023-05-05T00:00:00.000Z",
"total_added": "14597706.0446472999",
"total_removed": "3926967.723059737571037032",
"locked_amount": "2091922.905662142960425555307396306",
"locked_amount": "2038488.278459767293130894768769085",
"deposits": [
{
"amount": "129284.449",
@@ -50421,7 +50465,7 @@
"tranche_end": "2023-04-05T00:00:00.000Z",
"total_added": "5778205.3912159303",
"total_removed": "2749067.463242913023296295",
"locked_amount": "509627.9883313308999788596484477",
"locked_amount": "488515.643953737443539557205122125",
"deposits": [
{
"amount": "552496.6455",
@@ -52447,8 +52491,8 @@
"tranche_start": "2022-06-05T00:00:00.000Z",
"tranche_end": "2023-06-05T00:00:00.000Z",
"total_added": "472355.6199999996",
"total_removed": "33643.6376997992685",
"locked_amount": "141376.01010506265186415083105024",
"total_removed": "33803.2676021352685",
"locked_amount": "138789.542549975765711077066869624",
"deposits": [
{
"amount": "3000",
@@ -59132,6 +59176,16 @@
"user": "0x4F4A140E9B4a8403792970C6b5535ed0AA32c8A4",
"tx": "0x181a9afe9b3592427f1ee10f205e01f34ae7be383294e8d93e397766092bf6df"
},
{
"amount": "18.399238968",
"user": "0xD3ec605d078326B0a636ca90d496Ebb5Eb457a27",
"tx": "0xa82e451753cbced76b064af62868d3170dff7afca557680e657c27ebba5c7beb"
},
{
"amount": "141.230663368",
"user": "0xb7df334217A208B5B35a3824D1CDdBCa85BD352B",
"tx": "0x41e9fa9b5003c1d6edf1e9423bd6ae7ce6e4e48fcbce0b47ba17968a67da4d48"
},
{
"amount": "13.1116203702",
"user": "0x4cBC0C88d8FE503f62823B42f30a4900292C13bE",
@@ -63747,10 +63801,17 @@
"tx": "0x1c2c084e3efafb2ffa09c0b259bd229cce0d6da04dd092d911c7e6136a7ac8a9"
}
],
"withdrawals": [],
"withdrawals": [
{
"amount": "141.230663368",
"user": "0xb7df334217A208B5B35a3824D1CDdBCa85BD352B",
"tranche_id": 5,
"tx": "0x41e9fa9b5003c1d6edf1e9423bd6ae7ce6e4e48fcbce0b47ba17968a67da4d48"
}
],
"total_tokens": "200",
"withdrawn_tokens": "0",
"remaining_tokens": "200"
"withdrawn_tokens": "141.230663368",
"remaining_tokens": "58.769336632"
},
{
"address": "0x287a34EB03418ad55c2e93d0E4FB1e18DCE607ED",
@@ -67290,6 +67351,12 @@
}
],
"withdrawals": [
{
"amount": "18.399238968",
"user": "0xD3ec605d078326B0a636ca90d496Ebb5Eb457a27",
"tranche_id": 5,
"tx": "0xa82e451753cbced76b064af62868d3170dff7afca557680e657c27ebba5c7beb"
},
{
"amount": "262.76406646",
"user": "0xD3ec605d078326B0a636ca90d496Ebb5Eb457a27",
@@ -67298,8 +67365,8 @@
}
],
"total_tokens": "400",
"withdrawn_tokens": "262.76406646",
"remaining_tokens": "137.23593354"
"withdrawn_tokens": "281.163305428",
"remaining_tokens": "118.836694572"
},
{
"address": "0x7f6aba7563Cb5d31980D440337D3d1A6e3dB58F3",
@@ -63,6 +63,39 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
.and('have.text', 'Browse, vote, and propose');
});
});
it('should show open or enacted proposals with proposal summary', function () {
cy.get('body').then(($body) => {
if (!$body.find('[data-testid="proposals-list-item"]').length) {
cy.createMarket();
cy.reload();
cy.wait_for_spinner();
}
});
cy.getByTestId('proposals-list-item')
.should('have.length.at.least', 1)
.first()
.within(() => {
cy.getByTestId('proposal-title')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-type')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-description')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-details')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('proposal-status')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('vote-details')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('view-proposal-btn').should('be.visible');
});
});
it('should have external link for governance', function () {
cy.getByTestId('home-proposals').within(() => {
cy.getByTestId('external-link')
@@ -30,11 +30,11 @@ context('Staking Page - verify elements on page', function () {
describe('with wallets disconnected', { tags: '@smoke' }, function () {
describe('description section', function () {
it('Should have staking tab highlighted', function () {
it('Should have validators tab highlighted', function () {
cy.verify_tab_highlighted('validators');
});
it('Should have STAKING ON VEGA header visible', function () {
it('Should have validators ON VEGA header visible', function () {
cy.verify_page_header('Validators');
});
@@ -338,8 +338,8 @@ context(
.contains(currency.id)
.parent()
.siblings()
.within(() => cy.contains_exactly('10.00').should('be.visible'));
.invoke('text')
.should('not.be.empty');
cy.get(vegaWalletCurrencyTitle)
.contains(currency.id)
.parent()
@@ -18,7 +18,7 @@ export const NavDropDown = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
<AppNavLink
name={
<NavDropdownMenuTrigger
className="w-auto flex items-center"
className="w-auto flex items-center -m-3 p-3 cursor-pointer"
data-testid="state-trigger"
onClick={() => setOpen(!isOpen)}
>
+33 -3
View File
@@ -1,13 +1,21 @@
import { Link } from 'react-router-dom';
import { NetworkSwitcher } from '@vegaprotocol/environment';
import { useEffect, useState } from 'react';
import { TOP_LEVEL_ROUTES } from '../../routes/routes';
import { TOP_LEVEL_ROUTES, TOKEN_DROPDOWN_ROUTES } from '../../routes/routes';
import { useTranslation } from 'react-i18next';
import logoWhiteText from '../../images/logo-white-text.png';
import logoBlackText from '../../images/logo-black-text.png';
import debounce from 'lodash/debounce';
import { NavDrawer } from './nav-draw';
import { Nav as ToolkitNav } from '@vegaprotocol/ui-toolkit';
import {
Nav as ToolkitNav,
Navbar,
NavigationContent,
NavigationItem,
NavigationLink,
NavigationList,
NavigationTrigger,
} from '@vegaprotocol/ui-toolkit';
import { AppNavLink } from './nav-link';
import { NavDropDown } from './nav-dropdown';
@@ -41,7 +49,28 @@ export const Nav = ({ navbarTheme = 'inherit' }: NavbarProps) => {
const { t } = useTranslation();
const isYellow = navbarTheme === 'yellow';
return (
<Navbar>
<NavigationList>
{TOP_LEVEL_ROUTES.map((r) => (
<NavigationItem key={r.path}>
<NavigationLink path={r.path}>{t(r.name)}</NavigationLink>
</NavigationItem>
))}
<NavigationItem>
<NavigationTrigger>{t('Token')}</NavigationTrigger>
<NavigationContent backText={t('Token')}>
{TOKEN_DROPDOWN_ROUTES.map((r) => (
<NavigationItem>
<NavigationLink path={r.path}>{r.name}</NavigationLink>
</NavigationItem>
))}
</NavigationContent>
</NavigationItem>
</NavigationList>
</Navbar>
);
/*
return (
<ToolkitNav
navbarTheme={navbarTheme}
@@ -78,4 +107,5 @@ export const Nav = ({ navbarTheme = 'inherit' }: NavbarProps) => {
)}
</ToolkitNav>
);
*/
};
+1 -1
View File
@@ -35,7 +35,7 @@ export const TOP_LEVEL_ROUTES = [
export const TOKEN_DROPDOWN_ROUTES = [
{
name: 'Token',
name: 'Information',
path: Routes.TOKEN,
},
{
@@ -50,101 +50,117 @@ export const StandbyPendingValidatorsTable = ({
const nodes = useMemo(() => {
if (!data) return [];
return data.map(
({
id,
name,
avatarUrl,
stakedByDelegates,
stakedByOperator,
stakedTotal,
rankingScore: { stakeScore },
pendingStake,
}) => {
const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
const overstakedAmount = getOverstakedAmount(
rawValidatorScore,
stakedTotal,
totalStake
);
let individualStakeNeededForPromotion,
individualStakeNeededForPromotionDescription;
if (stakeNeededForPromotion && performanceScore) {
const stakedTotalBigNum = new BigNumber(stakedTotal);
const stakeNeededBigNum = new BigNumber(stakeNeededForPromotion);
const performanceScoreBigNum = new BigNumber(performanceScore);
const calc = stakeNeededBigNum
.dividedBy(performanceScoreBigNum)
.minus(stakedTotalBigNum);
if (calc.isGreaterThan(0)) {
individualStakeNeededForPromotion = calc.toString();
individualStakeNeededForPromotionDescription = t(
stakeNeededForPromotionDescription,
{
prefix: formatNumber(calc, 2).toString(),
}
);
} else {
individualStakeNeededForPromotion = '0';
individualStakeNeededForPromotionDescription = t(
stakeNeededForPromotionDescription,
{
prefix: formatNumber(0, 2).toString(),
}
);
}
}
return data
.sort((a, b) => {
const aVotingPower = new BigNumber(a.rankingScore.votingPower);
const bVotingPower = new BigNumber(b.rankingScore.votingPower);
return bVotingPower.minus(aVotingPower).toNumber();
})
.map((node, index) => {
const votingPowerRanking = index + 1;
return {
...node,
votingPowerRanking,
};
})
.map(
({
id,
[ValidatorFields.VALIDATOR]: {
avatarUrl,
name,
},
[ValidatorFields.STAKE]: formatNumber(
toBigNum(stakedTotal, decimals),
2
),
[ValidatorFields.STAKE_NEEDED_FOR_PROMOTION]:
individualStakeNeededForPromotion || null,
[ValidatorFields.STAKE_NEEDED_FOR_PROMOTION_DESCRIPTION]:
individualStakeNeededForPromotionDescription || t('n/a'),
[ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore),
[ValidatorFields.STAKED_BY_DELEGATES]: formatNumber(
toBigNum(stakedByDelegates, decimals),
2
),
[ValidatorFields.STAKED_BY_OPERATOR]: formatNumber(
toBigNum(stakedByOperator, decimals),
2
),
[ValidatorFields.PERFORMANCE_SCORE]:
getFormattedPerformanceScore(performanceScore).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]:
getPerformancePenalty(performanceScore),
[ValidatorFields.OVERSTAKED_AMOUNT]: overstakedAmount.toString(),
[ValidatorFields.OVERSTAKING_PENALTY]: getOverstakingPenalty(
overstakedAmount,
totalStake
),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
name,
avatarUrl,
stakedByDelegates,
stakedByOperator,
stakedTotal,
rankingScore: { stakeScore },
pendingStake,
votingPowerRanking,
}) => {
const { rawValidatorScore, performanceScore } =
getLastEpochScoreAndPerformance(previousEpochData, id);
const overstakedAmount = getOverstakedAmount(
rawValidatorScore,
performanceScore,
stakedTotal,
totalStake
),
[ValidatorFields.PENDING_STAKE]: formatNumber(
toBigNum(pendingStake, decimals),
2
),
};
}
);
);
let individualStakeNeededForPromotion,
individualStakeNeededForPromotionDescription;
if (stakeNeededForPromotion && performanceScore) {
const stakedTotalBigNum = new BigNumber(stakedTotal);
const stakeNeededBigNum = new BigNumber(stakeNeededForPromotion);
const performanceScoreBigNum = new BigNumber(performanceScore);
const calc = stakeNeededBigNum
.dividedBy(performanceScoreBigNum)
.minus(stakedTotalBigNum);
if (calc.isGreaterThan(0)) {
individualStakeNeededForPromotion = calc.toString();
individualStakeNeededForPromotionDescription = t(
stakeNeededForPromotionDescription,
{
prefix: formatNumber(calc, 2).toString(),
}
);
} else {
individualStakeNeededForPromotion = '0';
individualStakeNeededForPromotionDescription = t(
stakeNeededForPromotionDescription,
{
prefix: formatNumber(0, 2).toString(),
}
);
}
}
return {
id,
[ValidatorFields.RANKING_INDEX]: votingPowerRanking,
[ValidatorFields.VALIDATOR]: {
avatarUrl,
name,
},
[ValidatorFields.STAKE]: formatNumber(
toBigNum(stakedTotal, decimals),
2
),
[ValidatorFields.STAKE_NEEDED_FOR_PROMOTION]:
individualStakeNeededForPromotion || null,
[ValidatorFields.STAKE_NEEDED_FOR_PROMOTION_DESCRIPTION]:
individualStakeNeededForPromotionDescription || t('n/a'),
[ValidatorFields.STAKE_SHARE]: stakedTotalPercentage(stakeScore),
[ValidatorFields.STAKED_BY_DELEGATES]: formatNumber(
toBigNum(stakedByDelegates, decimals),
2
),
[ValidatorFields.STAKED_BY_OPERATOR]: formatNumber(
toBigNum(stakedByOperator, decimals),
2
),
[ValidatorFields.PERFORMANCE_SCORE]:
getFormattedPerformanceScore(performanceScore).toString(),
[ValidatorFields.PERFORMANCE_PENALTY]:
getPerformancePenalty(performanceScore),
[ValidatorFields.OVERSTAKED_AMOUNT]: overstakedAmount.toString(),
[ValidatorFields.OVERSTAKING_PENALTY]: getOverstakingPenalty(
overstakedAmount,
totalStake
),
[ValidatorFields.TOTAL_PENALTIES]: getTotalPenalties(
rawValidatorScore,
performanceScore,
stakedTotal,
totalStake
),
[ValidatorFields.PENDING_STAKE]: formatNumber(
toBigNum(pendingStake, decimals),
2
),
};
}
);
}, [
data,
decimals,
@@ -158,6 +174,12 @@ export const StandbyPendingValidatorsTable = ({
const StandbyPendingTable = forwardRef<AgGridReact>((_, gridRef) => {
const colDefs = useMemo<ColDef[]>(
() => [
{
field: ValidatorFields.RANKING_INDEX,
headerName: '#',
width: 60,
pinned: 'left',
},
{
field: ValidatorFields.VALIDATOR,
headerName: t(ValidatorFields.VALIDATOR).toString(),
+8 -2
View File
@@ -59,11 +59,17 @@ export const getOverstakedAmount = (
export const getOverstakingPenalty = (
overstakedAmount: BigNumber,
stakedOnNode: string
) =>
formatNumberPercentage(
) => {
// avoid division by zero
if (new BigNumber(stakedOnNode).isZero() || overstakedAmount.isZero()) {
return '0';
}
return formatNumberPercentage(
overstakedAmount.dividedBy(new BigNumber(stakedOnNode)).times(100),
2
);
};
export const getTotalPenalties = (
rawValidatorScore: string | null | undefined,
@@ -3,6 +3,8 @@ import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { marketsQuery } from '@vegaprotocol/mock';
import { getDateTimeFormat } from '@vegaprotocol/react-helpers';
const dialogCloseBtn = 'dialog-close';
describe('markets table', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.clearLocalStorage().then(() => {
@@ -147,7 +149,7 @@ describe('markets table', { tags: '@smoke' }, () => {
});
cy.visit('#/markets/market-0');
cy.url().should('contain', 'market-0');
cy.getByTestId('dialog-close').click();
cy.getByTestId(dialogCloseBtn).click();
cy.getByTestId('item-value').contains('Opening auction').realHover();
cy.getByTestId('opening-auction-sub-status').should(
'contain.text',
@@ -171,7 +173,8 @@ describe('markets table', { tags: '@smoke' }, () => {
});
function openMarketDropDown() {
cy.getByTestId('dialog-close').click();
cy.getByTestId(dialogCloseBtn).should('be.visible');
cy.getByTestId(dialogCloseBtn).click();
cy.getByTestId('popover-trigger').click();
cy.contains('Loading market data...').should('not.exist');
}
@@ -23,6 +23,7 @@ describe('Desktop view', { tags: '@smoke' }, () => {
cy.wrap(element)
.get('span.absolute.md\\:h-1.w-full')
.should('exist');
cy.contains('No market').should('not.exist');
cy.location('hash').should('equal', hashes[index]);
});
});
@@ -68,6 +69,7 @@ describe('Mobile view', { tags: '@smoke' }, () => {
cy.getByTestId('button-menu-drawer').click();
cy.getByTestId('menu-drawer').within((el) => {
cy.wrap(el).getByTestId('Trading').click();
cy.contains('No market').should('not.exist');
cy.location('hash').should('equal', '#/markets/market-1');
});
});
+9 -1
View File
@@ -96,7 +96,15 @@ export const MarketPage = () => {
const tradeView = useMemo(() => {
if (w > 960) {
return <TradeGrid market={data} onSelect={onSelect} />;
return (
<TradeGrid
market={data}
onSelect={onSelect}
pinnedAsset={
data?.tradableInstrument.instrument.product.settlementAsset
}
/>
);
}
return (
<TradePanels
@@ -28,6 +28,7 @@ import { NO_MARKET } from './constants';
import { LiquidityContainer } from '../liquidity/liquidity';
import { useNavigate } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import type { PinnedAsset } from '@vegaprotocol/accounts';
type MarketDependantView =
| typeof CandlesChartContainer
@@ -65,14 +66,17 @@ type TradingView = keyof typeof TradingViews;
interface TradeGridProps {
market: Market | null;
onSelect: (marketId: string) => void;
pinnedAsset?: PinnedAsset;
}
const MainGrid = ({
marketId,
onSelect,
pinnedAsset,
}: {
marketId: string;
onSelect?: (marketId: string) => void;
pinnedAsset?: PinnedAsset;
}) => {
const navigate = useNavigate();
const onMarketClick = (marketId: string) => {
@@ -175,7 +179,7 @@ const MainGrid = ({
</Tab>
<Tab id="accounts" name={t('Collateral')}>
<VegaWalletContainer>
<TradingViews.Collateral />
<TradingViews.Collateral pinnedAsset={pinnedAsset} />
</VegaWalletContainer>
</Tab>
</Tabs>
@@ -186,11 +190,19 @@ const MainGrid = ({
};
const MainGridWrapped = memo(MainGrid);
export const TradeGrid = ({ market, onSelect }: TradeGridProps) => {
export const TradeGrid = ({
market,
onSelect,
pinnedAsset,
}: TradeGridProps) => {
return (
<div className="h-full grid grid-rows-[min-content_1fr]">
<TradeMarketHeader market={market} onSelect={onSelect} />
<MainGridWrapped marketId={market?.id || ''} onSelect={onSelect} />
<MainGridWrapped
marketId={market?.id || ''}
onSelect={onSelect}
pinnedAsset={pinnedAsset}
/>
</div>
);
};
@@ -214,12 +226,14 @@ interface TradePanelsProps {
onSelect: (marketId: string) => void;
onMarketClick?: (marketId: string) => void;
onClickCollateral: () => void;
pinnedAsset?: PinnedAsset;
}
export const TradePanels = ({
market,
onSelect,
onClickCollateral,
pinnedAsset,
}: TradePanelsProps) => {
const [view, setView] = useState<TradingView>('Candles');
const renderView = () => {
@@ -228,6 +242,7 @@ export const TradePanels = ({
onSelect: (marketId: string) => void;
onMarketClick?: (marketId: string) => void;
onClickCollateral: () => void;
pinnedAsset?: PinnedAsset;
}>(TradingViews[view]);
if (!Component) {
@@ -241,6 +256,7 @@ export const TradePanels = ({
marketId={market?.id}
onSelect={onSelect}
onClickCollateral={onClickCollateral}
pinnedAsset={pinnedAsset}
/>
);
};
@@ -6,7 +6,7 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
export const DepositsContainer = () => {
const { pubKey, isReadOnly } = useVegaWallet();
const { data, loading, error } = useDataProvider({
const { data, loading, error, reload } = useDataProvider({
dataProvider: depositsProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
@@ -27,6 +27,7 @@ export const DepositsContainer = () => {
error={error}
noDataCondition={(data) => !(data && data.length)}
noDataMessage={t('No deposits')}
reload={reload}
/>
</div>
</div>
@@ -10,7 +10,7 @@ import { VegaWalletContainer } from '../../components/vega-wallet-container';
export const WithdrawalsContainer = () => {
const { pubKey, isReadOnly } = useVegaWallet();
const { data, loading, error } = useDataProvider({
const { data, loading, error, reload } = useDataProvider({
dataProvider: withdrawalProvider,
variables: { partyId: pubKey || '' },
skip: !pubKey,
@@ -33,6 +33,7 @@ export const WithdrawalsContainer = () => {
error={error}
noDataCondition={(data) => !(data && data.length)}
noDataMessage={t('No withdrawals')}
reload={reload}
/>
</div>
</div>
@@ -5,10 +5,15 @@ import { useWithdrawalDialog } from '@vegaprotocol/withdraws';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { PinnedAsset } from '@vegaprotocol/accounts';
import { AccountManager, useTransferDialog } from '@vegaprotocol/accounts';
import { useDepositDialog } from '@vegaprotocol/deposits';
export const AccountsContainer = () => {
export const AccountsContainer = ({
pinnedAsset,
}: {
pinnedAsset?: PinnedAsset;
}) => {
const { pubKey, isReadOnly } = useVegaWallet();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const openWithdrawalDialog = useWithdrawalDialog((store) => store.open);
@@ -39,6 +44,7 @@ export const AccountsContainer = () => {
onClickWithdraw={openWithdrawalDialog}
onClickDeposit={openDepositDialog}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
/>
</div>
{!isReadOnly && (
+1 -1
View File
@@ -1 +1 @@
export * from './navbar';
export * from './navbar2';
+1 -1
View File
@@ -185,7 +185,7 @@ interface AppNavLinkProps {
onClick?: () => void;
}
const AppNavLink = ({
export const AppNavLink = ({
name,
path,
navbarTheme,
+295
View File
@@ -0,0 +1,295 @@
import { NavLink } from 'react-router-dom';
import { Vega } from '../icons/vega';
import { create } from 'zustand';
import * as NavigationMenu from '@radix-ui/react-navigation-menu';
import type { ReactNode } from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import { Icon, ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
import { NetworkSwitcher } from '@vegaprotocol/environment';
import classNames from 'classnames';
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
import { animated, useTransition, useSpring, config } from 'react-spring';
const BREAKPOINT = 760;
const useNav = create<{
drawerOpen: boolean;
value: string;
update: (state: Partial<{ drawerOpen: boolean; value: string }>) => void;
}>((set) => ({
drawerOpen: false,
value: '',
update: (state) => set(state),
}));
export const Navbar = ({ children }: { children: ReactNode }) => {
return (
<div className="px-4 border-b border-vega-light-200 dark:border-vega-dark-200">
<div className="flex items-center gap-2">
<div className="py-3 mr-4">
<Vega />
</div>
{window.innerWidth > BREAKPOINT ? (
<Menu>{children}</Menu>
) : (
<Drawer>{children}</Drawer>
)}
</div>
</div>
);
};
const Menu = ({ children }: { children: ReactNode }) => {
const { value, update } = useNav();
return (
<>
<div className="mr-4">
<NetworkSwitcher />
</div>
<NavigationMenu.Root
value={value}
onValueChange={(value) => update({ value })}
className="relative self-end"
>
{children}
</NavigationMenu.Root>
<div className="flex items-center gap-2 ml-auto">
<VegaWalletConnectButton />
<ThemeSwitcher />
</div>
</>
);
};
const Drawer = ({ children }: { children: ReactNode }) => {
const { drawerOpen, value, update } = useNav();
const transition = useTransition(drawerOpen, {
from: { opacity: 0, transform: 'translateX(100%)' },
enter: { opacity: 1, transform: 'translateX(0%)' },
leave: { opacity: 0, transform: 'translateX(100%)' },
config: {
...config.default,
duration: 200,
},
});
return (
<>
<div className="ml-auto">
<MenuButton open={false} onClick={() => update({ drawerOpen: true })} />
</div>
<Dialog.Root
open={drawerOpen}
onOpenChange={(open) => update({ drawerOpen: open })}
>
<Dialog.Portal forceMount={true}>
{transition((styles, isOpen) => {
return isOpen ? (
<>
<Dialog.Overlay
forceMount={true}
asChild={true}
className="fixed inset-0 bg-black/50"
>
<animated.div style={{ opacity: styles.opacity }} />
</Dialog.Overlay>
<Dialog.Content
forceMount={true}
asChild={true}
className="absolute top-0 right-0 py-4 px-6 w-10/12 border-l border-vega-dark-200 h-screen bg-white dark:bg-black text-black dark:text-white"
>
<animated.div style={{ transform: styles.transform }}>
<div className="absolute top-3 right-3">
<MenuButton
open={true}
onClick={() => update({ drawerOpen: false })}
/>
</div>
<div className="pt-10">
<div className="pb-6 mb-6 border-b border-vega-dark-200">
<NetworkSwitcher />
</div>
<NavigationMenu.Root
value={value}
onValueChange={(value) => update({ value })}
>
{children}
<NavigationMenu.Viewport className="absolute w-full h-full top-0 left-0 bg-black" />
</NavigationMenu.Root>
<div className="pt-6 mt-6 border-t border-vega-dark-200">
<ThemeSwitcher />
</div>
</div>
</animated.div>
</Dialog.Content>
</>
) : null;
})}
</Dialog.Portal>
</Dialog.Root>
</>
);
};
export const NavigationList = ({ children }: { children: ReactNode }) => {
return (
<NavigationMenu.List className="flex flex-col md:flex-row gap-2">
{children}
</NavigationMenu.List>
);
};
export const NavigationItem = ({ children }: { children: ReactNode }) => {
return (
<NavigationMenu.Item className="relative mb-4 md:mb-0 last:mb-0 md:mr-4 last:mr-0 whitespace-nowrap">
{children}
</NavigationMenu.Item>
);
};
export const NavigationLink = ({
children,
path,
}: {
children: ReactNode;
path: string;
}) => {
const update = useNav((store) => store.update);
const borderClasses = classNames(
'absolute h-[2px] w-full bottom-[-1px] left-0',
'bg-black dark:bg-vega-yellow'
);
return (
<NavigationMenu.Link
asChild={true}
onClick={() => update({ drawerOpen: false, value: '' })}
>
<NavLink to={path} className="inline-block md:pt-2 text-lg">
{({ isActive }) => {
const linkTextClasses = classNames('hover:text-white', {
'text-white': isActive,
'text-vega-dark-300': !isActive,
});
return (
<span className="block relative pb-1 md:pb-2">
<span className={linkTextClasses}>{children}</span>
{isActive && <span className={borderClasses} />}
</span>
);
}}
</NavLink>
</NavigationMenu.Link>
);
};
export const NavigationTrigger = ({ children }: { children: ReactNode }) => {
// eslint-disable-next-line
const preventHover = (event: any) => {
const e = event as Event;
if (window.innerWidth < BREAKPOINT) e.preventDefault();
};
return (
<NavigationMenu.Trigger
onPointerMove={preventHover}
onPointerLeave={preventHover}
onPointerEnter={preventHover}
className="w-full flex items-center justify-between text-vega-dark-300 hover:text-white text-lg md:py-2"
>
{children}
<span className="md:rotate-90 md:ml-3">
<Icon name="arrow-right" size={3} />
</span>
</NavigationMenu.Trigger>
);
};
export const NavigationContent = ({
children,
backText,
}: {
children: ReactNode;
backText: string;
}) => {
return (
<NavigationMenu.Content>
<NavigationContentWrapper backText={backText}>
{children}
</NavigationContentWrapper>
</NavigationMenu.Content>
);
};
const NavigationContentWrapper = ({
backText,
children,
}: {
backText: string;
children: ReactNode;
}) => {
const drawerConfig = {
from: { transform: 'translateX(100%)' },
to: { transform: 'translateX(0%)' },
config: {
...config,
duration: 200,
},
};
const menuConfig = {
from: { opacity: 0, transform: 'translateY(40px)' },
to: { opacity: 1, transform: 'translateY(0)' },
config: {
...config,
duration: 100,
},
};
const styles = useSpring(
window.innerWidth > BREAKPOINT ? menuConfig : drawerConfig
);
return (
<animated.div
style={styles}
className="pt-20 md:py-2 px-4 z-30 md:absolute md:mt-3 md:border border-vega-dark-200 bg-black dark:md:bg-vega-dark-100 md:rounded-xl"
>
<div className="flex items-center md:hidden mb-6 text-vega-dark-300 hover:text-white">
<Icon name="arrow-left" size={3} />
<span className="ml-2">{backText}</span>
</div>
<ul className="md:min-w-[250px]">{children}</ul>
</animated.div>
);
};
const MenuButton = ({
open,
onClick,
}: {
open: boolean;
onClick: () => void;
}) => (
<button
className={classNames(
'flex flex-col justify-around gap-3 p-2 relative z-30 h-[34px]',
{
'z-50': open,
}
)}
onClick={onClick}
data-testid="button-menu-drawer"
>
<div
className={classNames('w-[26px] h-[2px] transition-all', {
'translate-y-0 rotate-0 bg-white': !open,
// 'bg-black': !drawerOpen && navbarTheme === 'yellow',
'translate-y-[7.5px] rotate-45 bg-black dark:bg-white': open,
})}
/>
<div
className={classNames('w-[26px] h-[2px] transition-all', {
'translate-y-0 rotate-0 bg-white': !open,
// 'bg-black': !drawerOpen && navbarTheme === 'yellow',
'-translate-y-[7.5px] -rotate-45 bg-black dark:bg-white': open,
})}
/>
</button>
);
+81 -5
View File
@@ -1,6 +1,13 @@
import Head from 'next/head';
import type { AppProps } from 'next/app';
import { Navbar } from '../components/navbar';
import {
Navbar,
NavigationContent,
NavigationItem,
NavigationLink,
NavigationList,
NavigationTrigger,
} from '../components/navbar';
import { t } from '@vegaprotocol/react-helpers';
import {
useEagerConnect as useVegaEagerConnect,
@@ -28,12 +35,19 @@ import { Footer } from '../components/footer';
import { useMemo, useState } from 'react';
import DialogsContainer from './dialogs-container';
import ToastsManager from './toasts-manager';
import { HashRouter, useLocation, useSearchParams } from 'react-router-dom';
import {
HashRouter,
Link,
NavLink,
useLocation,
useSearchParams,
} from 'react-router-dom';
import { Connectors } from '../lib/vega-connectors';
import { ViewingBanner } from '../components/viewing-banner';
import { Banner } from '../components/banner';
import classNames from 'classnames';
import { AppLoader } from '../components/app-loader';
import { Links, Routes } from './client-router';
const DEFAULT_TITLE = t('Welcome to Vega trading!');
@@ -68,6 +82,12 @@ const TransactionsHandler = () => {
};
function AppBody({ Component }: AppProps) {
const { marketId } = useGlobalStore((store) => ({
marketId: store.marketId,
}));
const tradingPath = marketId
? Links[Routes.MARKET](marketId)
: Links[Routes.MARKET]();
const location = useLocation();
const { VEGA_ENV } = useEnvironment();
@@ -84,10 +104,66 @@ function AppBody({ Component }: AppProps) {
</Head>
<Title />
<div className={gridClasses}>
<Navbar
navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'}
/>
<Banner />
<Navbar
// navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'}
>
<NavigationList>
<NavigationItem>
<NavigationLink path={Links[Routes.MARKETS]()}>
Markets
</NavigationLink>
</NavigationItem>
<NavigationItem>
<NavigationLink path={tradingPath}>Trading</NavigationLink>
</NavigationItem>
<NavigationItem>
<NavigationLink path={Links[Routes.PORTFOLIO]()}>
Portfolio
</NavigationLink>
</NavigationItem>
<NavigationItem>
<NavigationTrigger>Dropdown</NavigationTrigger>
<NavigationContent backText="Token">
<NavigationItem>
<NavigationLink path={tradingPath}>Trading</NavigationLink>
</NavigationItem>
<NavigationItem>
<NavigationLink path={Links[Routes.PORTFOLIO]()}>
Portfolio
</NavigationLink>
</NavigationItem>
<NavigationItem>
<NavigationLink path="https://google.com">
Redeem Vested Tokens
</NavigationLink>
</NavigationItem>
</NavigationContent>
</NavigationItem>
<NavigationItem>
<NavigationTrigger>Another</NavigationTrigger>
<NavigationContent backText="Another">
<NavigationItem>
<NavigationLink path="https://google.com">Foo</NavigationLink>
</NavigationItem>
<NavigationItem>
<NavigationLink path="https://google.com">Bar</NavigationLink>
</NavigationItem>
<NavigationItem>
<NavigationLink path="https://google.com">Quz</NavigationLink>
</NavigationItem>
</NavigationContent>
</NavigationItem>
</NavigationList>
</Navbar>
<ViewingBanner />
<main data-testid={location.pathname}>
<Component />
+11 -3
View File
@@ -4,6 +4,7 @@ import type { AgGridReact } from 'ag-grid-react';
import { useRef, useMemo, memo } from 'react';
import type { AccountFields } from './accounts-data-provider';
import { aggregatedAccountsDataProvider } from './accounts-data-provider';
import type { PinnedAsset } from './accounts-table';
import { AccountTable } from './accounts-table';
interface AccountManagerProps {
@@ -12,6 +13,7 @@ interface AccountManagerProps {
onClickWithdraw?: (assetId?: string) => void;
onClickDeposit?: (assetId?: string) => void;
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
}
export const AccountManager = ({
@@ -20,11 +22,15 @@ export const AccountManager = ({
onClickDeposit,
partyId,
isReadOnly,
pinnedAsset,
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
const { data, loading, error } = useDataProvider<AccountFields[], never>({
const { data, loading, error, reload } = useDataProvider<
AccountFields[],
never
>({
dataProvider: aggregatedAccountsDataProvider,
variables,
});
@@ -32,12 +38,13 @@ export const AccountManager = ({
<div className="relative h-full">
<AccountTable
ref={gridRef}
rowData={data}
rowData={error ? [] : data}
onClickAsset={onClickAsset}
onClickDeposit={onClickDeposit}
onClickWithdraw={onClickWithdraw}
isReadOnly={isReadOnly}
noRowsOverlayComponent={() => null}
pinnedAsset={pinnedAsset}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
@@ -45,7 +52,8 @@ export const AccountManager = ({
noDataCondition={(data) => !(data && data.length)}
error={error}
loading={loading}
noDataMessage={t('No accounts')}
noDataMessage={pinnedAsset ? ' ' : t('No accounts')}
reload={reload}
/>
</div>
</div>
+62 -40
View File
@@ -72,52 +72,74 @@ describe('AccountsTable', () => {
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
});
const rows = await screen.findAllByRole('row');
expect(rows.length).toBe(6);
});
});
it('should get correct account data', () => {
const result = getAccountData([singleRow]);
const expected = [
{
asset: {
__typename: 'Asset',
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
},
available: '0',
balance: '0',
breakdown: [
{
__typename: 'AccountBalance',
asset: {
__typename: 'Asset',
it('should add first asset as pinned', async () => {
await act(async () => {
render(
<AccountTable
rowData={singleRowData}
onClickAsset={() => null}
isReadOnly={false}
pinnedAsset={{
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
},
available: '0',
balance: '125600000',
deposited: '125600000',
market: {
__typename: 'Market',
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'BTCUSD Monthly (30 Jun 2022)',
name: 'tBTC',
}}
/>
);
});
const rows = await screen.findAllByRole('row');
expect(rows.length).toBe(9);
});
it('should get correct account data', () => {
const result = getAccountData([singleRow]);
const expected = [
{
asset: {
__typename: 'Asset',
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
},
available: '0',
balance: '0',
breakdown: [
{
__typename: 'AccountBalance',
asset: {
__typename: 'Asset',
decimals: 5,
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
symbol: 'tBTC',
},
available: '0',
balance: '125600000',
deposited: '125600000',
market: {
__typename: 'Market',
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
name: 'BTCUSD Monthly (30 Jun 2022)',
},
},
},
type: 'ACCOUNT_TYPE_MARGIN',
used: '125600000',
},
type: 'ACCOUNT_TYPE_MARGIN',
used: '125600000',
},
],
deposited: '125600000',
type: 'ACCOUNT_TYPE_GENERAL',
used: '125600000',
},
];
expect(result).toEqual(expected);
],
deposited: '125600000',
type: 'ACCOUNT_TYPE_GENERAL',
used: '125600000',
},
];
expect(result).toEqual(expected);
});
});
+76 -32
View File
@@ -1,4 +1,4 @@
import { forwardRef, useState } from 'react';
import { forwardRef, useMemo, useState } from 'react';
import {
addDecimalsFormatNumber,
isNumeric,
@@ -14,6 +14,8 @@ import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
import type { VegaValueFormatterParams } from '@vegaprotocol/ui-toolkit';
import BreakdownTable from './breakdown-table';
import type { AccountFields } from './accounts-data-provider';
import type { Asset } from '@vegaprotocol/types';
import BigNumber from 'bignumber.js';
export interface GetRowsParams extends Omit<IGetRowsParams, 'successCallback'> {
successCallback(rowsThisBlock: AccountFields[], lastRow?: number): void;
@@ -23,6 +25,8 @@ export interface Datasource extends IDatasource {
getRows(params: GetRowsParams): void;
}
export type PinnedAsset = Pick<Asset, 'symbol' | 'name' | 'id' | 'decimals'>;
export interface AccountTableProps extends AgGridReactProps {
rowData?: AccountFields[] | null;
datasource?: Datasource;
@@ -30,12 +34,33 @@ export interface AccountTableProps extends AgGridReactProps {
onClickWithdraw?: (assetId: string) => void;
onClickDeposit?: (assetId: string) => void;
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
}
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
({ onClickAsset, onClickWithdraw, onClickDeposit, ...props }, ref) => {
const [openBreakdown, setOpenBreakdown] = useState(false);
const [breakdown, setBreakdown] = useState<AccountFields[] | null>(null);
const pinnedAssetId = props.pinnedAsset?.id;
const pinnedAssetRow = useMemo(() => {
const currentPinnedAssetRow = props.rowData?.find(
(row) => row.asset.id === pinnedAssetId
);
if (!currentPinnedAssetRow) {
if (props.pinnedAsset) {
return {
asset: props.pinnedAsset,
available: '0',
used: '0',
deposited: '0',
balance: '0',
};
}
}
return currentPinnedAssetRow;
}, [pinnedAssetId, props.pinnedAsset, props.rowData]);
return (
<>
<AgGrid
@@ -51,6 +76,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
sortable: true,
}}
{...props}
pinnedTopRowData={pinnedAssetRow ? [pinnedAssetRow] : undefined}
>
<AgGridColumn
headerName={t('Asset')}
@@ -138,37 +164,55 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
cellRenderer={({
data,
}: VegaICellRendererParams<AccountFields>) => {
return data ? (
<>
<ButtonLink
data-testid="breakdown"
onClick={() => {
setOpenBreakdown(!openBreakdown);
setBreakdown(data.breakdown || null);
}}
>
{t('Breakdown')}
</ButtonLink>
<span className="mx-1" />
<ButtonLink
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
{t('Deposit')}
</ButtonLink>
<span className="mx-1" />
<ButtonLink
data-testid="withdraw"
onClick={() =>
onClickWithdraw && onClickWithdraw(data.asset.id)
}
>
{t('Withdraw')}
</ButtonLink>
</>
) : null;
if (!data) return null;
else {
if (
data.asset.id === pinnedAssetId &&
new BigNumber(data.deposited).isLessThanOrEqualTo(0)
) {
return (
<ButtonLink
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
{t('Deposit to trade')}
</ButtonLink>
);
}
return (
<>
<ButtonLink
data-testid="breakdown"
onClick={() => {
setOpenBreakdown(!openBreakdown);
setBreakdown(data.breakdown || null);
}}
>
{t('Breakdown')}
</ButtonLink>
<span className="mx-1" />
<ButtonLink
data-testid="deposit"
onClick={() => {
onClickDeposit && onClickDeposit(data.asset.id);
}}
>
{t('Deposit')}
</ButtonLink>
<span className="mx-1" />
<ButtonLink
data-testid="withdraw"
onClick={() =>
onClickWithdraw && onClickWithdraw(data.asset.id)
}
>
{t('Withdraw')}
</ButtonLink>
</>
);
}
}}
/>
)}
@@ -25,6 +25,7 @@ export const DealTicketContainer = ({
data: marketData,
error: marketDataError,
loading: marketDataLoading,
reload,
} = useThrottledDataProvider(
{
dataProvider: marketDataProvider,
@@ -39,6 +40,7 @@ export const DealTicketContainer = ({
data={market && marketData}
loading={marketLoading || marketDataLoading}
error={marketError || marketDataError}
reload={reload}
>
{market && marketData ? (
<DealTicket
+10 -5
View File
@@ -8,10 +8,10 @@ export const useNodeHealth = () => {
const url = useEnvironment((store) => store.VEGA_URL);
const headerStore = useHeaderStore();
const headers = url ? headerStore[url] : undefined;
const { data, error, loading, stopPolling } = useStatisticsQuery({
pollInterval: 1000,
fetchPolicy: 'no-cache',
});
const { data, error, loading, startPolling, stopPolling } =
useStatisticsQuery({
fetchPolicy: 'no-cache',
});
const blockDiff = useMemo(() => {
if (!data?.statistics.blockHeight) {
@@ -28,8 +28,13 @@ export const useNodeHealth = () => {
useEffect(() => {
if (error) {
stopPolling();
return;
}
}, [error, stopPolling]);
if (!('Cypress' in window)) {
startPolling(1000);
}
}, [error, startPolling, stopPolling]);
return {
error,
+2 -1
View File
@@ -19,7 +19,7 @@ export const FillsManager = ({
}: FillsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const scrolledToTop = useRef(true);
const { data, error, loading, addNewRows, getRows } = useFillsList({
const { data, error, loading, addNewRows, getRows, reload } = useFillsList({
partyId,
marketId,
gridRef,
@@ -55,6 +55,7 @@ export const FillsManager = ({
data={data}
noDataMessage={t('No fills')}
noDataCondition={(data) => !(data && data.length)}
reload={reload}
/>
</div>
</div>
+2 -2
View File
@@ -78,7 +78,7 @@ export const useFillsList = ({
const variables = useMemo(() => ({ partyId, marketId }), [partyId, marketId]);
const { data, error, loading, load, totalCount } = useDataProvider<
const { data, error, loading, load, totalCount, reload } = useDataProvider<
(TradeEdge | null)[],
Trade[]
>({
@@ -95,5 +95,5 @@ export const useFillsList = ({
load,
newRows
);
return { data, error, loading, addNewRows, getRows };
return { data, error, loading, addNewRows, getRows, reload };
};
@@ -29,14 +29,19 @@ export const ProposalsList = () => {
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
};
}, []);
const { data, loading, error } = useDataProvider({
const { data, loading, error, reload } = useDataProvider({
dataProvider: proposalsListDataProvider,
variables,
});
const filteredData = getNewMarketProposals(data || []);
const { columnDefs, defaultColDef } = useColumnDefs();
return (
<AsyncRenderer loading={loading} error={error} data={filteredData}>
<AsyncRenderer
loading={loading}
error={error}
data={filteredData}
reload={reload}
>
<AgGrid
ref={gridRef}
domLayout="autoHeight"
@@ -35,6 +35,7 @@ query LedgerEntries(
node {
...LedgerEntry
}
cursor
}
pageInfo {
startCursor
+2 -1
View File
@@ -14,7 +14,7 @@ export type LedgerEntriesQueryVariables = Types.Exact<{
}>;
export type LedgerEntriesQuery = { __typename?: 'Query', ledgerEntries: { __typename?: 'AggregatedLedgerEntriesConnection', edges: Array<{ __typename?: 'AggregatedLedgerEntriesEdge', node: { __typename?: 'AggregatedLedgerEntry', vegaTime: any, quantity: string, assetId?: string | null, transferType?: Types.TransferType | null, toAccountType?: Types.AccountType | null, toAccountMarketId?: string | null, toAccountPartyId?: string | null, toAccountBalance: string, fromAccountType?: Types.AccountType | null, fromAccountMarketId?: string | null, fromAccountPartyId?: string | null, fromAccountBalance: string } } | null>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } };
export type LedgerEntriesQuery = { __typename?: 'Query', ledgerEntries: { __typename?: 'AggregatedLedgerEntriesConnection', edges: Array<{ __typename?: 'AggregatedLedgerEntriesEdge', cursor: string, node: { __typename?: 'AggregatedLedgerEntry', vegaTime: any, quantity: string, assetId?: string | null, transferType?: Types.TransferType | null, toAccountType?: Types.AccountType | null, toAccountMarketId?: string | null, toAccountPartyId?: string | null, toAccountBalance: string, fromAccountType?: Types.AccountType | null, fromAccountMarketId?: string | null, fromAccountPartyId?: string | null, fromAccountBalance: string } } | null>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } };
export const LedgerEntryFragmentDoc = gql`
fragment LedgerEntry on AggregatedLedgerEntry {
@@ -43,6 +43,7 @@ export const LedgerEntriesDocument = gql`
node {
...LedgerEntry
}
cursor
}
pageInfo {
startCursor
@@ -128,7 +128,11 @@ export const ledgerEntriesProvider = makeDerivedDataProvider<
const marketReceiver = markets.find(
(market: Market) => market.id === entry.toAccountMarketId
);
return { node: { ...entry, asset, marketSender, marketReceiver } };
const cursor = edge?.cursor;
return {
node: { ...entry, asset, marketSender, marketReceiver },
cursor,
};
});
}
);
@@ -172,14 +176,13 @@ export const useLedgerEntriesDataProvider = ({
data: (AggregatedLedgerEntriesEdge | null)[] | null;
totalCount?: number;
}) => {
dataRef.current = data;
totalCountRef.current = totalCount;
return updateGridData(dataRef, data, gridRef);
},
[gridRef]
);
const { data, error, loading, load, totalCount } = useDataProvider({
const { data, error, loading, load, totalCount, reload } = useDataProvider({
dataProvider: ledgerEntriesProvider,
update,
insert,
@@ -193,5 +196,5 @@ export const useLedgerEntriesDataProvider = ({
totalCountRef,
load
);
return { loading, error, data, getRows };
return { loading, error, data, getRows, reload };
};
@@ -16,6 +16,7 @@ export const ledgerEntriesQuery = (
edges: ledgerEntries.map((node) => ({
__typename: 'AggregatedLedgerEntriesEdge',
node,
cursor: 'cursor-1',
})),
pageInfo: {
startCursor:
+13 -6
View File
@@ -21,11 +21,12 @@ export const LedgerManager = ({ partyId }: LedgerManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [filter, setFilter] = useState<Filter | undefined>();
const { data, error, loading, getRows } = useLedgerEntriesDataProvider({
partyId,
filter,
gridRef,
});
const { data, error, loading, getRows, reload } =
useLedgerEntriesDataProvider({
partyId,
filter,
gridRef,
});
const onFilterChanged = useCallback(
(event: FilterChangedEvent) => {
@@ -38,7 +39,11 @@ export const LedgerManager = ({ partyId }: LedgerManagerProps) => {
},
[filter]
);
const getRowId = useCallback(
({ data }: { data: Types.AggregatedLedgerEntry }) =>
`${data.vegaTime}-${data.fromAccountPartyId}-${data.toAccountPartyId}`,
[]
);
return (
<div className="h-full relative">
<LedgerTable
@@ -46,6 +51,7 @@ export const LedgerManager = ({ partyId }: LedgerManagerProps) => {
rowModelType="infinite"
datasource={{ getRows }}
onFilterChanged={onFilterChanged}
getRowId={getRowId}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
@@ -54,6 +60,7 @@ export const LedgerManager = ({ partyId }: LedgerManagerProps) => {
data={data}
noDataMessage={t('No entries')}
noDataCondition={(data) => !(data && data.length)}
reload={reload}
/>
</div>
</div>
@@ -85,7 +85,7 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
[marketId, updateOrderbookData]
);
const { data, error, loading, flush } = useDataProvider({
const { data, error, loading, flush, reload } = useDataProvider({
dataProvider: marketDepthProvider,
update,
variables,
@@ -156,6 +156,7 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
loading={loading || marketDataLoading || marketLoading}
error={error || marketDataError || marketError}
data={data}
reload={reload}
>
<Orderbook
{...orderbookData}
@@ -69,14 +69,14 @@ export const MarketInfoContainer = ({
[marketId, yTimestamp]
);
const { data, loading, error } = useDataProvider({
const { data, loading, error, reload } = useDataProvider({
dataProvider: marketInfoDataProvider,
skipUpdates: true,
variables,
});
return (
<AsyncRenderer data={data} loading={loading} error={error}>
<AsyncRenderer data={data} loading={loading} error={error} reload={reload}>
{data && data.market ? (
<Info market={data.market} onSelect={(id) => onSelect?.(id)} />
) : (
@@ -10,7 +10,7 @@ interface MarketsContainerProps {
}
export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
const { data, error, loading } = useDataProvider({
const { data, error, loading, reload } = useDataProvider({
dataProvider,
skipUpdates: true,
});
@@ -18,7 +18,8 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
return (
<div className="h-full relative">
<MarketListTable
rowData={data}
rowData={error ? [] : data}
noRowsOverlayComponent={() => null}
onRowClicked={(rowEvent: RowClickedEvent) => {
const { data, event } = rowEvent;
// filters out clicks on the symbol column because it should display asset details
@@ -36,6 +37,7 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
error={error}
data={data}
noDataMessage={t('No markets')}
reload={reload}
/>
</div>
</div>
@@ -155,6 +155,7 @@ export const ordersProvider = makeDataProvider({
append,
first: 100,
},
additionalContext: { isEnlargedTimeout: true },
});
export const ordersWithMarketProvider = makeDerivedDataProvider<
@@ -83,14 +83,15 @@ export const OrderListManager = ({
const create = useVegaTransactionStore((state) => state.create);
const hasActiveOrder = useHasActiveOrder(marketId);
const { data, error, loading, addNewRows, getRows } = useOrderListData({
partyId,
marketId,
sort,
filter,
gridRef,
scrolledToTop,
});
const { data, error, loading, addNewRows, getRows, reload } =
useOrderListData({
partyId,
marketId,
sort,
filter,
gridRef,
scrolledToTop,
});
const onBodyScrollEnd = (event: BodyScrollEndEvent) => {
if (event.top === 0) {
@@ -128,7 +129,7 @@ export const OrderListManager = ({
return (
<>
<div className="h-full relative grid grid-rows-[1fr,min-content]">
<div className="h-full relative">
<div className="relative">
<OrderListTable
ref={gridRef}
rowModelType="infinite"
@@ -149,6 +150,8 @@ export const OrderListManager = ({
setEditOrder={setEditOrder}
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
hasActiveOrder={hasActiveOrder}
blockLoadDebounceMillis={100}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer
@@ -157,6 +160,7 @@ export const OrderListManager = ({
data={data}
noDataMessage={t('No orders')}
noDataCondition={(data) => !(data && data.length)}
reload={reload}
/>
</div>
</div>
@@ -119,7 +119,7 @@ export const useOrderListData = ({
[gridRef]
);
const { data, error, loading, load, totalCount } = useDataProvider({
const { data, error, loading, load, totalCount, reload } = useDataProvider({
dataProvider: ordersWithMarketProvider,
update,
insert,
@@ -133,5 +133,5 @@ export const useOrderListData = ({
load,
newRows
);
return { loading, error, data, addNewRows, getRows };
return { loading, error, data, addNewRows, getRows, reload };
};
@@ -33,20 +33,24 @@ export type OrderListTableProps = OrderListProps & {
setEditOrder: (order: Order) => void;
onMarketClick?: (marketId: string) => void;
isReadOnly: boolean;
hasActiveOrder?: boolean;
};
export const OrderListTable = forwardRef<AgGridReact, OrderListTableProps>(
({ cancel, setEditOrder, onMarketClick, ...props }, ref) => {
({ cancel, setEditOrder, onMarketClick, hasActiveOrder, ...props }, ref) => {
return (
<AgGrid
ref={ref}
overlayNoRowsTemplate="No orders"
overlayNoRowsTemplate={t('No orders')}
defaultColDef={{
flex: 1,
resizable: true,
filterParams: { buttons: ['reset'] },
}}
style={{ width: '100%', height: '100%' }}
style={{
width: '100%',
height: hasActiveOrder ? 'calc(100% - 46px)' : '100%',
}}
getRowId={({ data }) => data.id}
{...props}
>
+7 -2
View File
@@ -18,7 +18,11 @@ export const PositionsManager = ({
isReadOnly,
}: PositionsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const { data, error, loading } = usePositionsData(partyId, gridRef, true);
const { data, error, loading, reload } = usePositionsData(
partyId,
gridRef,
true
);
const create = useVegaTransactionStore((store) => store.create);
const onClose = ({
marketId,
@@ -51,7 +55,7 @@ export const PositionsManager = ({
return (
<div className="h-full relative">
<PositionsTable
rowData={data}
rowData={error ? [] : data}
ref={gridRef}
onMarketClick={onMarketClick}
onClose={onClose}
@@ -65,6 +69,7 @@ export const PositionsManager = ({
data={data}
noDataMessage={t('No positions')}
noDataCondition={(data) => !(data && data.length)}
reload={reload}
/>
</div>
</div>
@@ -25,7 +25,7 @@ export const usePositionsData = (
},
[gridRef, clientSideModel]
);
const { data, error, loading } = useDataProvider({
const { data, error, loading, reload } = useDataProvider({
dataProvider: positionsMetricsProvider,
update,
variables,
@@ -45,5 +45,6 @@ export const usePositionsData = (
error,
loading,
getRows,
reload,
};
};
@@ -80,6 +80,7 @@ export const useDataProvider = <
}
}, []);
const reload = useCallback((force = false) => {
initialized.current = false;
if (reloadRef.current) {
reloadRef.current(force);
}
+1 -1
View File
@@ -17,7 +17,7 @@ const getLastRow = (
lastRow = totalCount;
}
} else if (blockLength < endRow - startRow) {
lastRow = blockLength;
lastRow = blockLength + startRow;
}
return lastRow;
};
+2 -2
View File
@@ -82,7 +82,7 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
[]
);
const { data, error, loading, load, totalCount } = useDataProvider({
const { data, error, loading, load, totalCount, reload } = useDataProvider({
dataProvider: tradesWithMarketProvider,
update,
insert,
@@ -107,7 +107,7 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
};
return (
<AsyncRenderer loading={loading} error={error} data={data}>
<AsyncRenderer loading={loading} error={error} data={data} reload={reload}>
<TradesTable
ref={gridRef}
rowModelType={data?.length ? 'infinite' : 'clientSide'}
@@ -0,0 +1,23 @@
import { render, act, screen } from '@testing-library/react';
import { AsyncRenderer } from './async-renderer';
describe('AsyncRenderer', () => {
it('timeout error should render button', async () => {
const reload = jest.fn();
await act(() => {
render(
<AsyncRenderer
reload={reload}
error={new Error('Timeout exceeded')}
loading={false}
data={undefined}
/>
);
});
expect(screen.getByRole('button')).toBeInTheDocument();
await act(() => {
screen.getByRole('button').click();
});
expect(reload).toHaveBeenCalled();
});
});
@@ -1,6 +1,7 @@
import { Splash } from '../splash';
import type { ReactNode } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import { Button } from '../button';
interface AsyncRendererProps<T> {
loading: boolean;
@@ -12,6 +13,7 @@ interface AsyncRendererProps<T> {
children?: ReactNode | null;
render?: (data: T) => ReactNode;
noDataCondition?(data?: T): boolean;
reload?: () => void;
}
export function AsyncRenderer<T = object>({
@@ -24,15 +26,30 @@ export function AsyncRenderer<T = object>({
noDataCondition,
children,
render,
reload,
}: AsyncRendererProps<T>) {
if (error) {
if (!data) {
return (
<Splash>
{errorMessage
? errorMessage
: t(`Something went wrong: ${error.message}`)}
</Splash>
<div className="h-full flex items-center justify-center">
<div className="h-12 flex flex-col items-center">
<Splash>
{errorMessage
? errorMessage
: t(`Something went wrong: ${error.message}`)}
</Splash>
{reload && error.message === 'Timeout exceeded' && (
<Button
size="sm"
className="pointer-events-auto"
type="button"
onClick={reload}
>
{t('Try again')}
</Button>
)}
</div>
</div>
);
}
}
@@ -53,7 +53,7 @@ export const NavDropdownMenuContent = forwardRef<
<DropdownMenuPrimitive.Content
{...contentProps}
ref={forwardedRef}
className="bg-vega-dark-100 mt-4 py-4 rounded-xl border border-vega-dark-200 text-white"
className="z-10 bg-vega-dark-100 mt-4 py-4 rounded-xl border border-vega-dark-200 text-white"
align="start"
sideOffset={10}
/>
@@ -1 +1,2 @@
export * from './nav';
export * from './nav2';
+287
View File
@@ -0,0 +1,287 @@
import { NavLink } from 'react-router-dom';
import { create } from 'zustand';
import * as NavigationMenu from '@radix-ui/react-navigation-menu';
import type { ReactNode } from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import { Icon, ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
import { NetworkSwitcher } from '@vegaprotocol/environment';
import classNames from 'classnames';
import { animated, useTransition, useSpring, config } from 'react-spring';
const BREAKPOINT = 760;
const useNav = create<{
drawerOpen: boolean;
value: string;
update: (state: Partial<{ drawerOpen: boolean; value: string }>) => void;
}>((set) => ({
drawerOpen: false,
value: '',
update: (state) => set(state),
}));
export const Navbar = ({ children }: { children: ReactNode }) => {
return (
<div className="px-4 border-b border-vega-light-200 dark:border-vega-dark-200">
<div className="flex items-center gap-2">
<div className="py-3 mr-4">THE ICON</div>
{window.innerWidth > BREAKPOINT ? (
<Menu>{children}</Menu>
) : (
<Drawer>{children}</Drawer>
)}
</div>
</div>
);
};
const Menu = ({ children }: { children: ReactNode }) => {
const { value, update } = useNav();
return (
<>
<div className="mr-4">
<NetworkSwitcher />
</div>
<NavigationMenu.Root
value={value}
onValueChange={(value) => update({ value })}
className="relative self-end"
>
{children}
</NavigationMenu.Root>
<div className="flex items-center gap-2 ml-auto">
<ThemeSwitcher />
</div>
</>
);
};
const Drawer = ({ children }: { children: ReactNode }) => {
const { drawerOpen, value, update } = useNav();
const transition = useTransition(drawerOpen, {
from: { opacity: 0, transform: 'translateX(100%)' },
enter: { opacity: 1, transform: 'translateX(0%)' },
leave: { opacity: 0, transform: 'translateX(100%)' },
config: {
...config.default,
duration: 200,
},
});
return (
<>
<div className="ml-auto">
<MenuButton open={false} onClick={() => update({ drawerOpen: true })} />
</div>
<Dialog.Root
open={drawerOpen}
onOpenChange={(open) => update({ drawerOpen: open })}
>
<Dialog.Portal forceMount={true}>
{transition((styles, isOpen) => {
return isOpen ? (
<>
<Dialog.Overlay
forceMount={true}
asChild={true}
className="fixed inset-0 bg-black/50"
>
<animated.div style={{ opacity: styles.opacity }} />
</Dialog.Overlay>
<Dialog.Content
forceMount={true}
asChild={true}
className="absolute top-0 right-0 py-4 px-6 w-10/12 border-l border-vega-dark-200 h-screen bg-white dark:bg-black text-black dark:text-white"
>
<animated.div style={{ transform: styles.transform }}>
<div className="absolute top-3 right-3">
<MenuButton
open={true}
onClick={() => update({ drawerOpen: false })}
/>
</div>
<div className="pt-10">
<div className="pb-6 mb-6 border-b border-vega-dark-200">
<NetworkSwitcher />
</div>
<NavigationMenu.Root
value={value}
onValueChange={(value) => update({ value })}
>
{children}
<NavigationMenu.Viewport className="absolute w-full h-full top-0 left-0 bg-black" />
</NavigationMenu.Root>
<div className="pt-6 mt-6 border-t border-vega-dark-200">
<ThemeSwitcher />
</div>
</div>
</animated.div>
</Dialog.Content>
</>
) : null;
})}
</Dialog.Portal>
</Dialog.Root>
</>
);
};
export const NavigationList = ({ children }: { children: ReactNode }) => {
return (
<NavigationMenu.List className="flex flex-col md:flex-row gap-2">
{children}
</NavigationMenu.List>
);
};
export const NavigationItem = ({ children }: { children: ReactNode }) => {
return (
<NavigationMenu.Item className="relative mb-4 md:mb-0 last:mb-0 md:mr-4 last:mr-0 whitespace-nowrap">
{children}
</NavigationMenu.Item>
);
};
export const NavigationLink = ({
children,
path,
}: {
children: ReactNode;
path: string;
}) => {
const update = useNav((store) => store.update);
const borderClasses = classNames(
'absolute h-[2px] w-full bottom-[-1px] left-0',
'bg-black dark:bg-vega-yellow'
);
return (
<NavigationMenu.Link
asChild={true}
onClick={() => update({ drawerOpen: false, value: '' })}
>
<NavLink to={path} className="inline-block md:pt-2 text-lg">
{({ isActive }) => {
const linkTextClasses = classNames('hover:text-white', {
'text-white': isActive,
'text-vega-dark-300': !isActive,
});
return (
<span className="block relative pb-1 md:pb-2">
<span className={linkTextClasses}>{children}</span>
{isActive && <span className={borderClasses} />}
</span>
);
}}
</NavLink>
</NavigationMenu.Link>
);
};
export const NavigationTrigger = ({ children }: { children: ReactNode }) => {
// eslint-disable-next-line
const preventHover = (event: any) => {
const e = event as Event;
if (window.innerWidth < BREAKPOINT) e.preventDefault();
};
return (
<NavigationMenu.Trigger
onPointerMove={preventHover}
onPointerLeave={preventHover}
onPointerEnter={preventHover}
className="w-full flex items-center justify-between text-vega-dark-300 hover:text-white text-lg md:py-2"
>
{children}
<span className="md:rotate-90 md:ml-3">
<Icon name="arrow-right" size={3} />
</span>
</NavigationMenu.Trigger>
);
};
export const NavigationContent = ({
children,
backText,
}: {
children: ReactNode;
backText: string;
}) => {
return (
<NavigationMenu.Content>
<NavigationContentWrapper backText={backText}>
{children}
</NavigationContentWrapper>
</NavigationMenu.Content>
);
};
const NavigationContentWrapper = ({
backText,
children,
}: {
backText: string;
children: ReactNode;
}) => {
const drawerConfig = {
from: { transform: 'translateX(100%)' },
to: { transform: 'translateX(0%)' },
config: {
...config,
duration: 200,
},
};
const menuConfig = {
from: { opacity: 0, transform: 'translateY(40px)' },
to: { opacity: 1, transform: 'translateY(0)' },
config: {
...config,
duration: 100,
},
};
const styles = useSpring(
window.innerWidth > BREAKPOINT ? menuConfig : drawerConfig
);
return (
<animated.div
style={styles}
className="pt-20 md:py-2 px-4 z-30 md:absolute md:mt-3 md:border border-vega-dark-200 bg-black dark:md:bg-vega-dark-100 md:rounded-xl"
>
<div className="flex items-center md:hidden mb-6 text-vega-dark-300 hover:text-white">
<Icon name="arrow-left" size={3} />
<span className="ml-2">{backText}</span>
</div>
<ul className="md:min-w-[250px]">{children}</ul>
</animated.div>
);
};
const MenuButton = ({
open,
onClick,
}: {
open: boolean;
onClick: () => void;
}) => (
<button
className={classNames(
'flex flex-col justify-around gap-3 p-2 relative h-[34px]'
)}
onClick={onClick}
data-testid="button-menu-drawer"
>
<div
className={classNames('w-[26px] h-[2px] transition-all', {
'translate-y-0 rotate-0 bg-white': !open,
// 'bg-black': !drawerOpen && navbarTheme === 'yellow',
'translate-y-[7.5px] rotate-45 bg-black dark:bg-white': open,
})}
/>
<div
className={classNames('w-[26px] h-[2px] transition-all', {
'translate-y-0 rotate-0 bg-white': !open,
// 'bg-black': !drawerOpen && navbarTheme === 'yellow',
'-translate-y-[7.5px] -rotate-45 bg-black dark:bg-white': open,
})}
/>
</button>
);
+2
View File
@@ -27,6 +27,7 @@
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.2",
"@radix-ui/react-icons": "^1.1.1",
"@radix-ui/react-navigation-menu": "^1.1.1",
"@radix-ui/react-popover": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.1",
"@radix-ui/react-select": "^1.2.0",
@@ -73,6 +74,7 @@
"react-i18next": "^11.11.4",
"react-intersection-observer": "^9.2.2",
"react-router-dom": "6.3.0",
"react-spring": "^9.6.1",
"react-syntax-highlighter": "^15.4.5",
"react-use-websocket": "^3.0.0",
"react-virtualized-auto-sizer": "^1.0.6",
+119
View File
@@ -4323,6 +4323,27 @@
aria-hidden "^1.1.1"
react-remove-scroll "2.5.5"
"@radix-ui/react-navigation-menu@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.1.1.tgz#84f24d90e6448a0c83d3431c6eefbea73dc7522e"
integrity sha512-Khgf+LwqYfUpbFAHcFPDMj6ZrWxnwCgC96liLYwE48x9YJbXGlutOWzZaSzrgl82xS+PwoPLQunfDe/i4ZITRA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.0"
"@radix-ui/react-collection" "1.0.1"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-context" "1.0.0"
"@radix-ui/react-direction" "1.0.0"
"@radix-ui/react-dismissable-layer" "1.0.2"
"@radix-ui/react-id" "1.0.0"
"@radix-ui/react-presence" "1.0.0"
"@radix-ui/react-primitive" "1.0.1"
"@radix-ui/react-use-callback-ref" "1.0.0"
"@radix-ui/react-use-controllable-state" "1.0.0"
"@radix-ui/react-use-layout-effect" "1.0.0"
"@radix-ui/react-use-previous" "1.0.0"
"@radix-ui/react-visually-hidden" "1.0.1"
"@radix-ui/react-popover@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.3.tgz#65ae2ee1fca2d7fd750308549eb8e0857c6160fe"
@@ -4576,6 +4597,92 @@
dependencies:
"@babel/runtime" "^7.13.10"
"@react-spring/animated@~9.6.1":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.6.1.tgz#ccc626d847cbe346f5f8815d0928183c647eb425"
integrity sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==
dependencies:
"@react-spring/shared" "~9.6.1"
"@react-spring/types" "~9.6.1"
"@react-spring/core@~9.6.1":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.6.1.tgz#ebe07c20682b360b06af116ea24e2b609e778c10"
integrity sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==
dependencies:
"@react-spring/animated" "~9.6.1"
"@react-spring/rafz" "~9.6.1"
"@react-spring/shared" "~9.6.1"
"@react-spring/types" "~9.6.1"
"@react-spring/konva@~9.6.1":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@react-spring/konva/-/konva-9.6.1.tgz#66e63da0e9681e42395e995402a7e73ba6892461"
integrity sha512-MevnU+tnG1LPsmMRpfJfevfLtI0ObIvrwYc+Xg+kmYJe00vwMRSdulQOztKANKalFXBewwk72XrQCeRLXFaUIg==
dependencies:
"@react-spring/animated" "~9.6.1"
"@react-spring/core" "~9.6.1"
"@react-spring/shared" "~9.6.1"
"@react-spring/types" "~9.6.1"
"@react-spring/native@~9.6.1":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@react-spring/native/-/native-9.6.1.tgz#b66e237f2faaa4f88569d5a03b6fb0136bcdf2b9"
integrity sha512-ZIfSytxFGLw4gYOb8gsmwG0+JZYxuM/Y1XPCXCkhuoMn+RmOYrr0kQ4gLczbmf+TRxth7OT1c8vBYz0+SCGcIQ==
dependencies:
"@react-spring/animated" "~9.6.1"
"@react-spring/core" "~9.6.1"
"@react-spring/shared" "~9.6.1"
"@react-spring/types" "~9.6.1"
"@react-spring/rafz@~9.6.1":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.6.1.tgz#d71aafb92b78b24e4ff84639f52745afc285c38d"
integrity sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==
"@react-spring/shared@~9.6.1":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.6.1.tgz#4e2e4296910656c02bd9fd54c559702bc836ac4e"
integrity sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==
dependencies:
"@react-spring/rafz" "~9.6.1"
"@react-spring/types" "~9.6.1"
"@react-spring/three@~9.6.1":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@react-spring/three/-/three-9.6.1.tgz#095fcd1dc6509127c33c14486d88289b89baeb9d"
integrity sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA==
dependencies:
"@react-spring/animated" "~9.6.1"
"@react-spring/core" "~9.6.1"
"@react-spring/shared" "~9.6.1"
"@react-spring/types" "~9.6.1"
"@react-spring/types@~9.6.1":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.6.1.tgz#913d3a68c5cbc1124fdb18eff919432f7b6abdde"
integrity sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==
"@react-spring/web@~9.6.1":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.6.1.tgz#3e4c03b724d2b545dc2fa2649eb6109318ab9178"
integrity sha512-X2zR6q2Z+FjsWfGAmAXlQaoUHbPmfuCaXpuM6TcwXPpLE1ZD4A1eys/wpXboFQmDkjnrlTmKvpVna1MjWpZ5Hw==
dependencies:
"@react-spring/animated" "~9.6.1"
"@react-spring/core" "~9.6.1"
"@react-spring/shared" "~9.6.1"
"@react-spring/types" "~9.6.1"
"@react-spring/zdog@~9.6.1":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@react-spring/zdog/-/zdog-9.6.1.tgz#5292c374e23e3846db3eb9d7557ed5a7bb40dada"
integrity sha512-0jSGm2OFW/+/+4dkRp46KzEkcLVfzV2k6DO1om0dLDtQ4q6FpX4dmDTlRc7Apzin6VtfQONMFIGITtbqoS28MQ==
dependencies:
"@react-spring/animated" "~9.6.1"
"@react-spring/core" "~9.6.1"
"@react-spring/shared" "~9.6.1"
"@react-spring/types" "~9.6.1"
"@repeaterjs/repeater@^3.0.4":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.4.tgz#a04d63f4d1bf5540a41b01a921c9a7fddc3bd1ca"
@@ -19261,6 +19368,18 @@ react-smooth@^2.0.1:
fast-equals "^2.0.0"
react-transition-group "2.9.0"
react-spring@^9.6.1:
version "9.6.1"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-9.6.1.tgz#e715b2fa523c1a3acfdcf1aaa93e081620b8cc8e"
integrity sha512-BeP80R4SLb1bZHW/Q62nECoScHw/fH+jzGkD7dc892HNGa+lbGIJXURc6U7N8JfZ8peEO46nPxR57aUMuYzquQ==
dependencies:
"@react-spring/core" "~9.6.1"
"@react-spring/konva" "~9.6.1"
"@react-spring/native" "~9.6.1"
"@react-spring/three" "~9.6.1"
"@react-spring/web" "~9.6.1"
"@react-spring/zdog" "~9.6.1"
react-style-singleton@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4"