feat(2033): reorganise token site (#2313)

* chore: make proposals the home page, redirect home to proposals

* chore: reorganise the remainder of the routes

* chore: small styling bug

* fix: nav links highlighted incorrectly

* feat: rename title, rename routes to new structure, add redirects

* chore: make navbar full width

* feat: new navbar bases on trading navbar

* chore: revert change to the width of the page

* chore: make navbar reactive and support draw

* chore: move draw into its' own file

* chore: move nav into toolkit

* style: lint

* chore: trading to use navbar

* fix: uppercase navbar title

* chore: add test

* fix: merge issue

* style: lint

* test: adjust test URLs

* test: more route adjsuting

* test: fix route name

* test: change URL to be new url

* test: more path corrections

* test: more path fixes

* style: lint

* test: minor test fixes

* test: more test fixes

* test: fix incorrect paths

* test: green build plz

* chore: adjust name as per PR comments

* test: fix path

* test: fix incorrect path

* test: final test fix

* test: plz green
This commit is contained in:
Dexter Edwards 2022-12-06 16:00:37 +00:00 committed by GitHub
parent 7c15ee6c01
commit 3486244137
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 637 additions and 507 deletions

View File

@ -52,7 +52,6 @@ context(
function () {
before('connect wallets and set approval limit', function () {
cy.visit('/');
cy.verify_page_header('The $VEGA token');
cy.get_network_parameters().then((network_parameters) => {
cy.wrap(
network_parameters['spam.protection.proposal.min.tokens'] /
@ -132,7 +131,7 @@ context(
cy.wait_for_spinner();
cy.connectVegaWallet();
cy.ethereum_wallet_connect();
cy.navigate_to('governance');
cy.navigate_to('proposals');
cy.wait_for_spinner();
});
@ -207,7 +206,7 @@ context(
let arrayOfProposals = [];
cy.navigate_to('governance');
cy.navigate_to('proposals');
cy.wait_for_spinner();
cy.get(proposalDetailsTitle)
.each((proposalTitleElement) => {
@ -223,16 +222,16 @@ context(
it('Able to submit a valid freeform proposal - with minimum required tokens associated - but also staked', function () {
cy.ensure_specified_unstaked_tokens_are_associated('2');
cy.navigate_to_page_if_not_already_loaded('governance');
cy.navigate_to_page_if_not_already_loaded('proposals');
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', '2');
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.wait_for_spinner();
cy.click_on_validator_from_list(0);
cy.staking_validator_page_add_stake('2');
cy.get(vegaWalletStakedBalances, txTimeout).should('contain', '2');
cy.navigate_to('governance');
cy.navigate_to('proposals');
cy.wait_for_spinner();
cy.go_to_make_new_proposal(governanceProposalType.FREEFORM);
cy.enter_unique_freeform_proposal_body('50', generateProposalTitle());
@ -320,14 +319,14 @@ context(
cy.ensure_specified_unstaked_tokens_are_associated(
tokensRequiredToAchieveResult
);
cy.navigate_to_page_if_not_already_loaded('governance');
cy.navigate_to_page_if_not_already_loaded('proposals');
cy.get('@submittedProposal').within(() =>
cy.get(viewProposalButton).click()
);
cy.get_proposal_information_from_table('Participation met')
.contains('👍')
.should('be.visible');
cy.navigate_to('governance');
cy.navigate_to('proposals');
cy.wait_for_spinner();
cy.get('@submittedProposal').within(() =>
cy.get(voteStatus).should('have.text', 'Participation met')
@ -378,7 +377,7 @@ context(
cy.wait_for_proposal_submitted();
cy.wait_for_proposal_sync();
cy.navigate_to('governance');
cy.navigate_to('proposals');
cy.wait_for_spinner();
cy.get_submitted_proposal_from_proposal_list(proposalTitle).within(
() => cy.get(viewProposalButton).click()
@ -523,7 +522,7 @@ context(
cy.ensure_specified_unstaked_tokens_are_associated(
tokensRequiredToAchieveResult
);
cy.navigate_to_page_if_not_already_loaded('governance');
cy.navigate_to_page_if_not_already_loaded('proposals');
cy.get('@submittedProposal').within(() =>
cy.get(viewProposalButton).click()
);
@ -611,7 +610,7 @@ context(
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
cy.get(dialogCloseButton).click();
cy.wait_for_proposal_sync();
cy.navigate_to('governance');
cy.navigate_to('proposals');
cy.wait_for_spinner();
cy.get(rejectProposalsLink).click().wait_for_spinner();
cy.get('@rawProposal').then((rawProposal) => {
@ -808,7 +807,7 @@ context(
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.wait_for_proposal_submitted();
cy.wait_for_proposal_sync();
cy.navigate_to('governance');
cy.navigate_to('proposals');
cy.wait_for_spinner();
}
@ -820,7 +819,7 @@ context(
cy.wait_for_proposal_submitted();
cy.wait_for_proposal_sync();
cy.get(proposalDetailsTitle).invoke('text').as('proposalTitle');
cy.navigate_to('governance');
cy.navigate_to('proposals');
cy.wait_for_spinner();
}

View File

@ -41,7 +41,6 @@ context(
function () {
before('connect wallets and set approval limit', function () {
cy.visit('/');
cy.verify_page_header('The $VEGA token');
cy.vega_wallet_set_specified_approval_amount('1000');
});
@ -50,7 +49,7 @@ context(
cy.wait_for_spinner();
cy.connectVegaWallet();
cy.ethereum_wallet_connect();
cy.navigate_to('governance');
cy.navigate_to('proposals');
cy.wait_for_spinner();
});
@ -74,7 +73,7 @@ context(
});
it('Unable to submit network parameter with missing/invalid fields', function () {
cy.navigate_to_page_if_not_already_loaded('governance');
cy.navigate_to_page_if_not_already_loaded('proposals');
cy.go_to_make_new_proposal(governanceProposalType.NETWORK_PARAMETER);
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.get(inputError).should('have.length', 3);
@ -99,7 +98,7 @@ context(
});
it('Unable to submit network parameter proposal with vote deadline above enactment deadline', function () {
cy.navigate_to_page_if_not_already_loaded('governance');
cy.navigate_to_page_if_not_already_loaded('proposals');
cy.go_to_make_new_proposal(governanceProposalType.NETWORK_PARAMETER);
cy.get(newProposalTitle).type('Test update network parameter proposal');
cy.get(newProposalDescription).type('invalid deadlines');

View File

@ -33,7 +33,6 @@ context(
// 2001-STKE-002, 2001-STKE-032
before('visit staking tab and connect vega wallet', function () {
cy.visit('/');
cy.verify_page_header('The $VEGA token');
cy.vega_wallet_set_specified_approval_amount('1000');
});
@ -46,7 +45,7 @@ context(
cy.connectVegaWallet();
cy.ethereum_wallet_connect();
cy.vega_wallet_teardown();
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.wait_for_spinner();
}
);
@ -95,7 +94,7 @@ context(
.contains(2.0, epochTimeout)
.should('be.visible');
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.validate_validator_list_total_stake_and_share('0', '2.00', '100%');
});
@ -142,14 +141,14 @@ context(
.contains(2.0, epochTimeout)
.should('be.visible');
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.validate_validator_list_total_stake_and_share('0', '2.00', '100%');
});
it('Able to stake against a validator - using vega from both wallet and vesting contract', function () {
cy.staking_page_associate_tokens('3', { type: 'contract' });
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.staking_page_associate_tokens('4', { type: 'wallet' });
cy.get(vegaWalletUnstakedBalance, txTimeout).should(
@ -202,7 +201,7 @@ context(
.contains(6.0, epochTimeout)
.should('be.visible');
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.validate_validator_list_total_stake_and_share('0', '6.00', '100%');
});
@ -232,7 +231,7 @@ context(
.parent()
.should('contain', 2.0, txTimeout);
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.click_on_validator_from_list(1);
@ -253,7 +252,7 @@ context(
.eq(1)
.should('contain', 1.0, txTimeout);
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.wait_for_spinner();
cy.get(`[row-id="${0}"]`).within(() => {
@ -310,7 +309,7 @@ context(
txTimeout
);
cy.navigate_to('staking');
cy.navigate_to('validators');
// 2001-STKE-040
cy.click_on_validator_from_list(0);
@ -343,7 +342,7 @@ context(
cy.get(totalStake, epochTimeout).should('contain.text', '2');
cy.get(stakeShare, epochTimeout).should('have.text', '100%');
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.validate_validator_list_total_stake_and_share('0', '2.00', '100%');
});
@ -370,7 +369,7 @@ context(
txTimeout
);
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.click_on_validator_from_list('0');
@ -399,7 +398,7 @@ context(
txTimeout
);
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.validate_validator_list_total_stake_and_share('0', '0.00', '0%');
});
@ -429,7 +428,7 @@ context(
txTimeout
);
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.click_on_validator_from_list(0);
@ -470,7 +469,7 @@ context(
txTimeout
);
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.click_on_validator_from_list(0);
@ -536,7 +535,7 @@ context(
txTimeout
);
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.validate_validator_list_total_stake_and_share('0', '0.00', '0%');
});
@ -590,7 +589,7 @@ context(
txTimeout
);
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.validate_validator_list_total_stake_and_share('0', '0.00', '0%');
});
@ -637,7 +636,7 @@ context(
.should('contain', 2.0, txTimeout)
.and('contain', partValidatorId);
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.validate_validator_list_total_stake_and_share('0', '2.00', '100%');
});

View File

@ -7,11 +7,11 @@ const vegaWalletUnstakedBalance =
'[data-testid="vega-wallet-balance-unstaked"]';
const txTimeout = Cypress.env('txTimeout');
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
const ethWalletAssociateButton = '[href="/staking/associate"]';
const ethWalletAssociateButton = '[href="/validators/associate"]';
const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]';
const tokenAmountInputBox = '[data-testid="token-amount-input"]';
const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
const ethWalletDissociateButton = '[href="/staking/disassociate"]';
const ethWalletDissociateButton = '[href="/validators/disassociate"]';
const vestingContractSection = '[data-testid="vega-in-vesting-contract"]';
const vegaInWalletSection = '[data-testid="vega-in-wallet"]';
const connectedVegaKey = '[data-testid="connected-vega-key"]';
@ -27,7 +27,6 @@ context(
function () {
before('visit staking tab and connect vega wallet', function () {
cy.visit('/');
cy.verify_page_header('The $VEGA token');
cy.vega_wallet_set_specified_approval_amount('1000');
});
@ -40,7 +39,7 @@ context(
cy.connectVegaWallet();
cy.ethereum_wallet_connect();
cy.vega_wallet_teardown();
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.wait_for_spinner();
}
);
@ -257,7 +256,7 @@ context(
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 52);
});
cy.navigate_to('staking');
cy.navigate_to('validators');
cy.staking_page_disassociate_tokens('9', { type: 'wallet' });
cy.get(vegaInWalletSection).within(() => {

View File

@ -33,7 +33,7 @@ context(
beforeEach('Navigate to withdrawal page', function () {
cy.reload();
cy.visit('/');
cy.navigate_to('withdrawals');
cy.navigate_to('withdraw');
cy.wait_for_spinner();
cy.connectVegaWallet();
cy.ethereum_wallet_connect();

View File

@ -9,12 +9,12 @@ context(
{ tags: '@smoke' },
function () {
before('navigate to governance page', function () {
cy.visit('/').navigate_to('governance');
cy.visit('/').navigate_to('proposals');
});
describe('with no network change proposals', function () {
it('should have governance tab highlighted', function () {
cy.verify_tab_highlighted('governance');
cy.verify_tab_highlighted('proposals');
});
it('should have GOVERNANCE header visible', function () {
@ -49,7 +49,7 @@ context(
.should('be.visible')
.and('have.text', 'New proposal')
.and('have.attr', 'href')
.and('equal', '/governance/propose');
.and('equal', '/proposals/propose');
});
// Skipping this test for now, the new proposal button no longer takes a user directly
@ -60,7 +60,7 @@ context(
cy.get(connectToVegaWalletButton)
.should('be.visible')
.and('have.text', 'Connect Vega wallet');
cy.navigate_to('governance');
cy.navigate_to('proposals');
cy.wait_for_spinner();
});
});

View File

@ -1,10 +1,10 @@
const navSection = 'nav';
const navHome = '[href="/"]';
const navVesting = '[href="/vesting"]';
const navStaking = '[href="/staking"]';
const navVesting = '[href="/token/tranches"]';
const navToken = '[href="/token"]';
const navStaking = '[href="/validators"]';
const navRewards = '[href="/rewards"]';
const navWithdraw = '[href="/withdrawals"]';
const navGovernance = '[href="/governance"]';
const navWithdraw = '[href="/token/withdraw"]';
const navGovernance = '[href="/proposals"]';
const tokenDetailsTable = '.token-details';
const address = '[data-testid="token-address"]';
@ -25,7 +25,7 @@ const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
before('visit token home page', function () {
cy.visit('/');
cy.visit('/token');
});
describe('with wallets disconnected', function () {
@ -34,9 +34,9 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
});
describe('Navigation tabs', function () {
it('should have HOME tab', function () {
it('should have TOKEN tab', function () {
cy.get(navSection).within(() => {
cy.get(navHome).should('be.visible');
cy.get(navToken).should('be.visible');
});
});
it('should have VESTING tab', function () {
@ -106,14 +106,14 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
cy.get(tranchesLink)
.should('be.visible')
.and('have.attr', 'href')
.and('equal', '/tranches');
.and('equal', '/token/tranches');
});
it('should have REDEEM button', function () {
cy.get(redeemBtn)
.should('be.visible')
.parent()
.should('have.attr', 'href')
.and('equal', '/vesting');
.and('equal', '/token/redeem');
});
it('should have GET VEGA WALLET link', function () {
cy.get(getVegaWalletLink)
@ -125,21 +125,21 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
cy.get(associateVegaLink)
.should('be.visible')
.and('have.attr', 'href')
.and('equal', '/staking/associate');
.and('equal', '/validators/associate');
});
it('should have STAKING button', function () {
cy.get(stakingBtn)
.should('be.visible')
.parent()
.should('have.attr', 'href')
.and('equal', '/staking');
.and('equal', '/validators');
});
it('should have GOVERNANCE button', function () {
cy.get(governanceBtn)
.should('be.visible')
.parent()
.should('have.attr', 'href')
.and('equal', '/governance');
.and('equal', '/proposals');
});
});
});

View File

@ -15,13 +15,13 @@ const stakeNumberRegex = /^\d*\.?\d*$/;
context('Staking Page - verify elements on page', function () {
before('navigate to staking page', function () {
cy.visit('/').navigate_to('staking');
cy.visit('/').navigate_to('validators');
});
describe('with wallets disconnected', { tags: '@smoke' }, function () {
describe('description section', function () {
it('Should have staking tab highlighted', function () {
cy.verify_tab_highlighted('staking');
cy.verify_tab_highlighted('validators');
});
it('Should have STAKING ON VEGA header visible', function () {

View File

@ -5,11 +5,10 @@ context(
'Vesting Page - verify elements on page',
{ tags: '@smoke' },
function () {
before('navigate to vesting page', function () {
cy.visit('/').navigate_to('vesting');
});
describe('with wallets disconnected', function () {
before('navigate to vesting page', function () {
cy.visit('/').navigate_to('vesting');
});
it('should have vesting tab highlighted', function () {
cy.verify_tab_highlighted('vesting');
});
@ -32,15 +31,16 @@ context(
describe('with eth wallet connected', function () {
before('connect eth wallet', function () {
cy.ethereum_wallet_connect();
cy.visit('/');
});
// 1005-VEST-001
// 1005-VEST-002
it('Able to view tranches', function () {
cy.get('[href="/tranches"]')
.should('have.text', 'all tranches')
cy.get('[href="/token/tranches"]')
.should('have.text', 'Supply & Vesting')
.click();
cy.url().should('include', '/tranches');
cy.url().should('include', '/token/tranches');
cy.get('h1').should('contain.text', 'Vesting tranches');
});
});

View File

@ -2,8 +2,8 @@ const walletContainer = '[data-testid="ethereum-wallet"]';
const walletHeader = '[data-testid="wallet-header"] h1';
const connectToEthButton = '[data-testid="connect-to-eth-wallet-button"]';
const connectorList = '[data-testid="web3-connector-list"]';
const associate = '[href="/staking/associate"]';
const disassociate = '[href="/staking/disassociate"]';
const associate = '[href="/validators/associate"]';
const disassociate = '[href="/validators/disassociate"]';
const disconnect = '[data-testid="disconnect-from-eth-wallet-button"]';
const accountNo = '[data-testid="ethereum-account-truncated"]';
const currencyTitle = '[data-testid="currency-title"]';

View File

@ -18,8 +18,8 @@ const walletName = '[data-testid="wallet-name"]';
const currencyTitle = '[data-testid="currency-title"]';
const currencyValue = '[data-testid="currency-value"]';
const vegaUnstaked = '[data-testid="vega-wallet-balance-unstaked"] .text-right';
const governanceBtn = '[href="/governance"]';
const stakingBtn = '[href="/staking"]';
const governanceBtn = '[href="/proposals"]';
const stakingBtn = '[href="/validators"]';
const manageLink = '[data-testid="manage-vega-wallet"]';
const dialogVegaKey = '[data-testid="vega-public-key-full"]';
const dialogDisconnectBtn = '[data-testid="disconnect"]';

View File

@ -5,12 +5,12 @@ context(
{ tags: '@smoke' },
function () {
before('navigate to withdrawals page', function () {
cy.visit('/').navigate_to('withdrawals');
cy.visit('/').navigate_to('withdraw');
});
describe('with wallets disconnected', function () {
it('should have withdraw tab highlighted', function () {
cy.verify_tab_highlighted('withdrawals');
cy.verify_tab_highlighted('withdraw');
});
it('should have WITHDRAW header visible', function () {

View File

@ -8,13 +8,13 @@ Cypress.Commands.add(
const navigation = {
section: 'nav',
home: '[href="/"]',
vesting: '[href="/vesting"]',
staking: '[href="/staking"]',
vesting: '[href="/token/redeem"]',
validators: '[href="/validators"]',
rewards: '[href="/rewards"]',
withdrawals: '[href="/withdrawals"]',
governance: '[href="/governance"]',
withdraw: '[href="/token/withdraw"]',
proposals: '[href="/proposals"]',
pageSpinner: '[data-testid="splash-loader"]',
token: '[href="/token"]',
};
Cypress.Commands.add('navigate_to', (page) => {

View File

@ -172,9 +172,9 @@ Cypress.Commands.add('get_sort_order_of_supplied_array', (suppliedArray) => {
});
Cypress.Commands.add('go_to_make_new_proposal', (proposalType) => {
cy.navigate_to_page_if_not_already_loaded('governance');
cy.navigate_to_page_if_not_already_loaded('proposals');
cy.get(newProposalButton).should('be.visible').click();
cy.url().should('include', '/governance/propose');
cy.url().should('include', '/proposals/propose');
cy.wait_for_spinner();
cy.get('li').contains(proposalType).click();
});

View File

@ -3,8 +3,8 @@ const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
const tokenInputApprove = '[data-testid="token-input-approve-button"]';
const addStakeRadioButton = '[data-testid="add-stake-radio"]';
const removeStakeRadioButton = '[data-testid="remove-stake-radio"]';
const ethWalletAssociateButton = '[href="/staking/associate"]';
const ethWalletDissociateButton = '[href="/staking/disassociate"]';
const ethWalletAssociateButton = '[href="/validators/associate"]';
const ethWalletDissociateButton = '[href="/validators/disassociate"]';
const vegaWalletUnstakedBalance =
'[data-testid="vega-wallet-balance-unstaked"]';
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';

View File

@ -88,7 +88,7 @@ const Web3Container = ({
<AppLoader>
<BalanceManager>
<>
<div className="app w-full max-w-[1500px] mx-auto grid grid-rows-[1fr_min-content] min-h-full border-neutral-700 lg:border-l lg:border-r lg:text-body-large">
<div className="app w-full max-w-[1500px] mx-auto grid grid-rows-[min-content_1fr_min-content] min-h-full border-neutral-700 lg:border-l lg:border-r lg:text-body-large">
<TemplateSidebar sidebar={sideBar}>
<AppRouter />
</TemplateSidebar>

View File

@ -164,12 +164,12 @@ const ConnectedKey = () => {
)}
</section>
<WalletCardActions>
<Link className="flex-1" to={`${Routes.STAKING}/associate`}>
<Link className="flex-1" to={`${Routes.VALIDATORS}/associate`}>
<Button size="sm" fill={true}>
{t('associate')}
</Button>
</Link>
<Link className="flex-1" to={`${Routes.STAKING}/disassociate`}>
<Link className="flex-1" to={`${Routes.VALIDATORS}/disassociate`}>
<Button size="sm" fill={true}>
{t('disassociate')}
</Button>

View File

@ -0,0 +1,124 @@
import classNames from 'classnames';
import { NavLink } from 'react-router-dom';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
import * as Dialog from '@radix-ui/react-dialog';
import { EthWallet } from '../eth-wallet';
import { VegaWallet } from '../vega-wallet';
interface Route {
name: string;
path: string;
}
const DrawerSection = ({ children }: { children: React.ReactNode }) => (
<div className="px-4 my-4">{children}</div>
);
const IconLine = ({ inverted }: { inverted: boolean }) => (
<span className={`block w-6 h-[2px] ${inverted ? 'bg-black' : 'bg-white'}`} />
);
const DrawerNavLinks = ({
isInverted,
routes,
}: {
isInverted?: boolean;
routes: Route[];
}) => {
const { appDispatch } = useAppState();
const linkProps = {
end: true,
onClick: () =>
appDispatch({ type: AppStateActionType.SET_DRAWER, isOpen: false }),
};
const navClasses = classNames('flex flex-col');
return (
<nav className={navClasses}>
{routes.map(({ name, path }) => {
return (
<NavLink
{...linkProps}
to={{ pathname: path }}
className={({ isActive }) =>
classNames({
'bg-vega-yellow text-black': !isInverted && isActive,
'bg-transparent text-white hover:text-vega-yellow':
!isInverted && !isActive,
'bg-black text-white': isInverted && isActive,
'bg-transparent text-black hover:text-white':
isInverted && !isActive,
'border-t border-white p-4': true,
})
}
>
{name}
</NavLink>
);
})}
</nav>
);
};
export const NavDrawer = ({
inverted,
routes,
}: {
inverted: boolean;
routes: Route[];
}) => {
const { appState, appDispatch } = useAppState();
const drawerContentClasses = classNames(
'drawer-content', // needed for css animation
// Positions the modal in the center of screen
'fixed w-[80vw] max-w-[420px] top-0 right-0',
'flex flex-col flex-nowrap justify-between h-full bg-banner overflow-y-scroll border-l border-white',
'bg-black text-neutral-200'
);
return (
<>
<button
onClick={() =>
appDispatch({
type: AppStateActionType.SET_DRAWER,
isOpen: true,
})
}
className="flex flex-col flex-nowrap gap-1"
>
<IconLine inverted={inverted} />
<IconLine inverted={inverted} />
<IconLine inverted={inverted} />
</button>
<Dialog.Root
open={appState.drawerOpen}
onOpenChange={(isOpen) =>
appDispatch({
type: AppStateActionType.SET_DRAWER,
isOpen,
})
}
>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-white/15" />
<Dialog.Content className={drawerContentClasses}>
<div>
<DrawerSection>
<EthWallet />
</DrawerSection>
<DrawerSection>
<VegaWallet />
</DrawerSection>
</div>
<DrawerNavLinks routes={routes} />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</>
);
};

View File

@ -1,91 +1,22 @@
import './nav.css';
import classNames from 'classnames';
import debounce from 'lodash/debounce';
import React from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import { useTranslation } from 'react-i18next';
import { Link, NavLink } from 'react-router-dom';
import vegaWhite from '../../images/vega_white.png';
import { Flags } from '../../config';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
import { NavLink, Link } from 'react-router-dom';
import { NetworkSwitcher } from '@vegaprotocol/environment';
import type { HTMLAttributeAnchorTarget } from 'react';
import { useEffect, useState } from 'react';
import Routes from '../../routes/routes';
import { EthWallet } from '../eth-wallet';
import { VegaWallet } from '../vega-wallet';
import { useTranslation } from 'react-i18next';
import vegaWhite from '../../images/vega_white.png';
import debounce from 'lodash/debounce';
import { NavDrawer } from './nav-draw';
import {
getNavLinkClassNames,
Nav as ToolkitNav,
} from '@vegaprotocol/ui-toolkit';
const Fish = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="33"
height="20"
viewBox="0 0 200 116"
fill="none"
data-testid="fairground-icon"
>
<g clipPath="url(#clip0)">
<path
d="M70.5918 32.8569L70.5918 22.7932L60.5254 22.7932L60.5254 32.8569L70.5918 32.8569Z"
fill="black"
/>
<path
d="M80.6641 83.2006L80.6641 73.1377L70.5977 73.1377L70.5977 83.2006L80.6641 83.2006Z"
fill="black"
/>
<path
d="M70.5918 93.2409L70.5918 83.1772L60.5254 83.1772L60.5254 93.2409L70.5918 93.2409Z"
fill="black"
/>
<path
d="M100.797 93.2636L100.797 73.1377L90.7305 73.1377L90.7305 93.2636L100.797 93.2636Z"
fill="black"
/>
<path
d="M90.7285 103.33L90.7285 93.2671L80.662 93.2671L80.662 103.33L90.7285 103.33Z"
fill="black"
/>
<path
d="M90.7285 22.8026L90.7285 12.74L80.662 12.74L80.662 22.8026L90.7285 22.8026Z"
fill="black"
/>
<path
d="M110.869 12.6108L110.869 2.54785L100.803 2.54785L100.803 12.6108L110.869 12.6108Z"
fill="black"
/>
<path
d="M120.934 103.326L120.934 73.1377L110.867 73.1377L110.867 103.326L120.934 103.326Z"
fill="black"
/>
<path
d="M110.869 113.384L110.869 103.321L100.803 103.321L100.803 113.384L110.869 113.384Z"
fill="black"
/>
<path
d="M161.328 52.9855L161.328 42.9226L151.262 42.9226L151.262 52.9855L161.328 52.9855Z"
fill="black"
/>
<path
d="M20.133 83.187L30.3354 83.187L30.3354 73.124L40.4017 73.124L40.4017 63.0613L50.4681 63.0613L50.4681 73.124L60.5345 73.124L60.5345 63.0613L70.6008 63.0613L80.6672 63.0613L131.135 63.0613L131.135 113.376L161.334 113.376L161.334 103.313L171.4 103.313L171.4 93.25L181.467 93.25L181.467 83.187L191.533 83.187L191.533 63.0613L181.467 63.0613L181.467 73.1241L171.4 73.1241L171.4 83.187L161.334 83.187L161.334 73.1241L171.4 73.1241L171.4 63.0613L161.334 63.0613L151.268 63.0613L141.201 63.0613L141.201 52.9983L141.201 32.8726L161.334 32.8726L171.4 32.8726L171.4 63.0613L181.467 63.0613L181.467 52.9983L191.533 52.9983L191.533 32.8726L181.467 32.8726L181.467 22.8096L171.4 22.8096L171.4 12.7467L161.334 12.7467L161.334 2.54785L141.201 2.54785L131.135 2.54785L131.135 52.9983L120.933 52.9983L120.933 12.7467L110.866 12.7467L110.866 52.9983L100.8 52.9983L100.8 22.8096L90.7336 22.8096L90.7336 52.9983L80.6672 52.9983L80.6672 32.8726L70.6008 32.8726L70.6008 52.9983L60.5345 52.9983L60.5345 42.9354L50.4681 42.9354L50.4681 52.9983L40.4017 52.9983L40.4017 42.9354L30.3354 42.9354L30.3354 32.8726L20.133 32.8726L20.133 22.8096L0.000308081 22.8096L0.000307201 42.9354L10.0666 42.9354L10.0666 52.9983L20.133 52.9983L20.133 63.0613L10.0666 63.0613L10.0666 73.124L0.000305881 73.124L0.000305002 93.25L20.133 93.25L20.133 83.187Z"
fill="black"
/>
</g>
<defs>
<clipPath id="clip0">
<rect width="200" height="116" fill="none" />
</clipPath>
</defs>
</svg>
);
const useDebouncedResize = () => {
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
export const Nav = () => {
const [windowWidth, setWindowWidth] = React.useState(window.innerWidth);
const isDesktop = windowWidth > 959;
const inverted = Flags.FAIRGROUND;
React.useEffect(() => {
useEffect(() => {
const handleResizeDebounced = debounce(() => {
setWindowWidth(window.innerWidth);
}, 300);
@ -96,167 +27,116 @@ export const Nav = () => {
window.removeEventListener('resize', handleResizeDebounced);
};
}, []);
return (
<div
className={`px-4 py-2 ${
inverted
? 'bg-clouds bg-no-repeat bg-cover bg-vega-yellow'
: 'border-neutral-700 border-b'
}`}
>
{isDesktop && <NavHeader fairground={inverted} />}
<div className="flex justify-between items-center mx-auto gap-12 lg:justify-start">
{!isDesktop && <NavHeader fairground={inverted} />}
<div className="flex gap-12 lg:flex-auto">
{isDesktop ? (
<NavLinks isDesktop={isDesktop} isInverted={inverted} />
) : (
<NavDrawer inverted={inverted} />
)}
</div>
</div>
</div>
);
};
const NavHeader = ({ fairground }: { fairground: boolean }) => {
const { t } = useTranslation();
return (
<div className="flex items-center gap-4">
<Link to="/">
{fairground ? (
<Fish />
) : (
<img alt="Vega" src={vegaWhite} height={30} width={30} />
)}
</Link>
<h1
data-testid="header-title"
className={`uppercase md:text-2xl sm:text-lg font-alpha uppercase calt my-0 ${
fairground ? 'text-black' : 'text-white'
}`}
>
{fairground ? t('fairgroundTitle') : t('title')}
</h1>
</div>
);
};
const DrawerSection = ({ children }: { children: React.ReactNode }) => (
<div className="px-4 my-4">{children}</div>
);
const IconLine = ({ inverted }: { inverted: boolean }) => (
<span className={`block w-6 h-[2px] ${inverted ? 'bg-black' : 'bg-white'}`} />
);
const NavDrawer = ({ inverted }: { inverted: boolean }) => {
const { appState, appDispatch } = useAppState();
const drawerContentClasses = classNames(
'drawer-content', // needed for css animation
// Positions the modal in the center of screen
'fixed w-[80vw] max-w-[420px] top-0 right-0',
'flex flex-col flex-nowrap justify-between h-full bg-banner overflow-y-scroll border-l border-white',
'bg-black text-neutral-200'
);
return (
<>
<button
onClick={() =>
appDispatch({
type: AppStateActionType.SET_DRAWER,
isOpen: true,
})
}
className="flex flex-col flex-nowrap gap-1"
>
<IconLine inverted={inverted} />
<IconLine inverted={inverted} />
<IconLine inverted={inverted} />
</button>
<Dialog.Root
open={appState.drawerOpen}
onOpenChange={(isOpen) =>
appDispatch({
type: AppStateActionType.SET_DRAWER,
isOpen,
})
}
>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-white/15" />
<Dialog.Content className={drawerContentClasses}>
<div>
<DrawerSection>
<EthWallet />
</DrawerSection>
<DrawerSection>
<VegaWallet />
</DrawerSection>
</div>
<NavLinks isDesktop={false} />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</>
);
};
const NavLinks = ({
isDesktop,
isInverted,
}: {
isDesktop: boolean;
isInverted?: boolean;
}) => {
const { appDispatch } = useAppState();
const { t } = useTranslation();
const linkProps = {
onClick: () =>
appDispatch({ type: AppStateActionType.SET_DRAWER, isOpen: false }),
return {
windowWidth,
};
};
type NavbarTheme = 'inherit' | 'dark' | 'yellow';
interface NavbarProps {
navbarTheme?: NavbarTheme;
}
export const Nav = ({ navbarTheme = 'inherit' }: NavbarProps) => {
const { windowWidth } = useDebouncedResize();
const isDesktop = windowWidth > 995;
const { t } = useTranslation();
const isYellow = navbarTheme === 'yellow';
const routes = [
{ route: Routes.HOME, text: t('Home') },
{ route: Routes.VESTING, text: t('Vesting') },
{ route: Routes.STAKING, text: t('Staking') },
{ route: Routes.REWARDS, text: t('Rewards') },
{ route: Routes.WITHDRAWALS, text: t('Withdraw') },
{ route: Routes.GOVERNANCE, text: t('Governance') },
{
name: t('Proposals'),
path: Routes.PROPOSALS,
},
{
name: t('Validators'),
path: Routes.VALIDATORS,
},
{
name: t('Rewards'),
path: Routes.REWARDS,
},
{
name: t('Token'),
path: Routes.TOKEN,
},
{
name: t('Redeem'),
path: Routes.REDEEM,
},
{
name: t('Withdraw'),
path: Routes.WITHDRAWALS,
},
{
name: t('Supply & Vesting'),
path: Routes.TRANCHES,
},
];
const navClasses = classNames('flex', {
'flex-row gap-2 mt-4 uppercase': isDesktop,
'flex-col': !isDesktop,
});
return (
<nav className={navClasses}>
{routes.map(({ route, text }) => {
return (
<NavLink
{...linkProps}
to={route}
key={route}
className={({ isActive }) =>
classNames({
'bg-vega-yellow text-black': !isInverted && isActive,
'bg-transparent text-white hover:text-vega-yellow':
!isInverted && !isActive,
'bg-black text-white': isInverted && isActive,
'bg-transparent text-black hover:text-white':
isInverted && !isActive,
'py-1 px-2': isDesktop,
'border-t border-white p-4': !isDesktop,
})
}
>
{text}
</NavLink>
);
})}
</nav>
<ToolkitNav
navbarTheme={navbarTheme}
icon={
<Link to="/">
<img alt="Vega" src={vegaWhite} height={30} width={30} />
</Link>
}
title={t('Governance')}
titleContent={<NetworkSwitcher />}
>
{isDesktop ? (
<nav className="flex items-center flex-1 px-2">
{routes.map((r) => (
<AppNavLink {...r} navbarTheme={navbarTheme} />
))}
</nav>
) : (
<nav className="flex items-center flex-1 px-2 justify-end">
<NavDrawer inverted={isYellow} routes={routes} />
</nav>
)}
</ToolkitNav>
);
};
interface AppNavLinkProps {
name: string;
path: string;
navbarTheme: NavbarTheme;
testId?: string;
alignRight?: boolean;
target?: HTMLAttributeAnchorTarget;
}
const AppNavLink = ({
name,
path,
navbarTheme,
alignRight,
target,
testId = name,
}: AppNavLinkProps) => {
const borderClasses = classNames('absolute h-1 w-full bottom-[-1px] left-0', {
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
'bg-black': navbarTheme === 'yellow',
});
return (
<NavLink
data-testid={testId}
to={{ pathname: path }}
className={getNavLinkClassNames(navbarTheme, alignRight)}
target={target}
end={true}
>
{({ isActive }) => {
return (
<>
{name}
{isActive && <span className={borderClasses} />}
</>
);
}}
</NavLink>
);
};

View File

@ -1,3 +1,4 @@
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import React from 'react';
import { Nav } from '../nav';
@ -8,17 +9,20 @@ export interface TemplateSidebarProps {
}
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
const { VEGA_ENV } = useEnvironment();
return (
<div className="w-full border-b border-neutral-700 lg:grid lg:grid-rows-[min-content_1fr] lg:grid-cols-[1fr_450px]">
<Nav />
<main className="col-start-1 p-4">{children}</main>
<aside className="col-start-2 row-start-1 row-span-2 hidden lg:block p-4 bg-banner bg-contain border-l border-neutral-700">
{sidebar.map((Component, i) => (
<section className="mb-4 last:mb-0" key={i}>
{Component}
</section>
))}
</aside>
</div>
<>
<Nav navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
<div className="w-full border-b border-neutral-700 lg:grid lg:grid-rows-[min-content_1fr] lg:grid-cols-[1fr_450px]">
<main className="col-start-1 p-4">{children}</main>
<aside className="col-start-2 row-start-1 row-span-2 hidden lg:block p-4 bg-banner bg-contain border-l border-neutral-700">
{sidebar.map((Component, i) => (
<section className="mb-4 last:mb-0" key={i}>
{Component}
</section>
))}
</aside>
</div>
</>
);
}

View File

@ -22,20 +22,18 @@ export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
if (!pubKey) {
return (
<p>
<Button
data-testid="connect-to-vega-wallet-btn"
onClick={() => {
appDispatch({
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY,
isOpen: true,
});
openVegaWalletDialog();
}}
>
{t('connectVegaWallet')}
</Button>
</p>
<Button
data-testid="connect-to-vega-wallet-btn"
onClick={() => {
appDispatch({
type: AppStateActionType.SET_VEGA_WALLET_OVERLAY,
isOpen: true,
});
openVegaWalletDialog();
}}
>
{t('connectVegaWallet')}
</Button>
);
}

View File

@ -175,7 +175,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
label={`${d.name || truncateMiddle(d.nodeId)} ${
d.hasStakePending ? `(${t('thisEpoch')})` : ''
}`}
link={`${Routes.STAKING}/${d.nodeId}`}
link={`${Routes.VALIDATORS}/${d.nodeId}`}
value={d.currentEpochStake}
/>
</div>
@ -186,7 +186,7 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
label={`${d.name || truncateMiddle(d.nodeId)} (${t(
'nextEpoch'
)})`}
link={`${Routes.STAKING}/${d.nodeId}`}
link={`${Routes.VALIDATORS}/${d.nodeId}`}
value={d.nextEpochStake}
/>
</div>
@ -194,12 +194,12 @@ const VegaWalletConnected = ({ vegaKeys }: VegaWalletConnectedProps) => {
</div>
))}
<WalletCardActions>
<Link className="flex-1" to={Routes.GOVERNANCE}>
<Link className="flex-1" to={Routes.PROPOSALS}>
<Button size="sm" fill={true}>
{t('governance')}
</Button>
</Link>
<Link className="flex-1" to={Routes.STAKING}>
<Link className="flex-1" to={Routes.VALIDATORS}>
<Button size="sm" fill={true}>
{t('staking')}
</Button>

View File

@ -472,7 +472,7 @@
"transaction": "Transaction",
"associated": "Associated",
"notAssociated": "Not Associated",
"title": "VEGA TOKEN",
"title": "Governance",
"Use the Ethereum wallet you want to send your tokens to. You'll also need enough Ethereum to pay gas.": "Connect to the Ethereum wallet that holds your $VEGA tokens to see what can be redeemed from vesting tranches. To redeem tokens you will need some ETH to pay gas fees.\n",
"Connect to see your stake": "Connect to see your stake",
"Staked on Vega validator": "Associated to Vega key",
@ -715,5 +715,8 @@
"SpamProtectionMin": "Spam protection minimum",
"ListAsset": "List Asset",
"ListAssetDescription": "This asset needs to be listed on the collateral bridge before it can be used.",
"ListAssetAction": "List asset"
"ListAssetAction": "List asset",
"Proposals": "Proposals",
"Validators": "Validators",
"Redeem": "Redeem"
}

View File

@ -56,7 +56,7 @@ export const Complete = ({
</Link>
</p>
)}
<RouteLink to={Routes.VESTING}>
<RouteLink to={Routes.REDEEM}>
<Button>{t('Check your vesting VEGA tokens')}</Button>
</RouteLink>
</Callout>

View File

@ -196,7 +196,7 @@ export const ProposalsListItemDetails = ({
)}
{proposal.id && (
<div className="col-start-2 row-start-2 justify-self-end">
<Link to={`${Routes.GOVERNANCE}/${proposal.id}`}>
<Link to={`${Routes.PROPOSALS}/${proposal.id}`}>
<Button data-testid="view-proposal-btn" size="sm">
{t('View')}
</Button>

View File

@ -54,7 +54,7 @@ export const ProposalsList = ({ proposals }: ProposalsListProps) => {
<Link
className="xs:justify-self-end"
data-testid="new-proposal-link"
to={`${Routes.GOVERNANCE}/propose`}
to={`${Routes.PROPOSALS}/propose`}
>
<Button variant="primary" size="sm">
{t('NewProposal')}

View File

@ -45,7 +45,7 @@ export const Propose = () => {
<p>
<Link
className="underline"
to={`${Routes.GOVERNANCE}/propose/network-parameter`}
to={`${Routes.PROPOSALS}/propose/network-parameter`}
>
{t('NetworkParameter')}
</Link>
@ -55,7 +55,7 @@ export const Propose = () => {
<p>
<Link
className="underline"
to={`${Routes.GOVERNANCE}/propose/new-market`}
to={`${Routes.PROPOSALS}/propose/new-market`}
>
{t('NewMarket')}
</Link>
@ -65,7 +65,7 @@ export const Propose = () => {
<p>
<Link
className="underline"
to={`${Routes.GOVERNANCE}/propose/update-market`}
to={`${Routes.PROPOSALS}/propose/update-market`}
>
{t('UpdateMarket')}
</Link>
@ -75,7 +75,7 @@ export const Propose = () => {
<p>
<Link
className="underline"
to={`${Routes.GOVERNANCE}/propose/new-asset`}
to={`${Routes.PROPOSALS}/propose/new-asset`}
>
{t('NewAsset')}
</Link>
@ -85,7 +85,7 @@ export const Propose = () => {
<p>
<Link
className="underline"
to={`${Routes.GOVERNANCE}/propose/update-asset`}
to={`${Routes.PROPOSALS}/propose/update-asset`}
>
{t('UpdateAsset')}
</Link>
@ -95,7 +95,7 @@ export const Propose = () => {
<p>
<Link
className="underline"
to={`${Routes.GOVERNANCE}/propose/freeform`}
to={`${Routes.PROPOSALS}/propose/freeform`}
>
{t('Freeform')}
</Link>
@ -105,7 +105,7 @@ export const Propose = () => {
<p>
<Link
className="underline"
to={`${Routes.GOVERNANCE}/propose/raw`}
to={`${Routes.PROPOSALS}/propose/raw`}
>
{t('RawProposal')}
</Link>

View File

@ -57,7 +57,7 @@ const Home = ({ name }: RouteChildProps) => {
'Once unlocked they can be redeemed from the contract so that you can transfer them between wallets.'
)}
</p>
<Link to={Routes.VESTING}>
<Link to={Routes.REDEEM}>
<Button
variant="primary"
size="md"
@ -93,7 +93,7 @@ const Home = ({ name }: RouteChildProps) => {
<p>
<Link
data-testid="associate-vega-tokens-link-on-homepage"
to={`${Routes.STAKING}/associate`}
to={`${Routes.VALIDATORS}/associate`}
className="underline text-white"
>
{t('Associate VEGA tokens')}
@ -110,7 +110,7 @@ const Home = ({ name }: RouteChildProps) => {
)}
</p>
<p>
<Link to={Routes.STAKING}>
<Link to={Routes.VALIDATORS}>
<Button size="md" data-testid="staking-button-on-homepage">
{t('Nominate a validator')}
</Button>
@ -127,7 +127,7 @@ const Home = ({ name }: RouteChildProps) => {
)}
</p>
<p>
<Link to={Routes.GOVERNANCE}>
<Link to={Routes.PROPOSALS}>
<Button size="md" data-testid="governance-button-on-homepage">
{t('View Governance proposals')}
</Button>

View File

@ -10,7 +10,7 @@ import Routes from '../routes';
const RedemptionIndex = ({ name }: RouteChildProps) => {
useDocumentTitle(name);
const { t } = useTranslation();
const tranche = useMatch(`${Routes.VESTING}/:id`);
const tranche = useMatch(`${Routes.REDEEM}/:id`);
return (
<>

View File

@ -121,13 +121,13 @@ export const RedeemFromTranche = () => {
stakingLink: (
<Link
className="underline text-white"
to={Routes.STAKING}
to={Routes.VALIDATORS}
/>
),
governanceLink: (
<Link
className="underline text-white"
to={Routes.GOVERNANCE}
to={Routes.PROPOSALS}
/>
),
}}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Outlet } from 'react-router-dom';
import { Navigate, Outlet } from 'react-router-dom';
import Home from './home';
import NotFound from './not-found';
import NotPermitted from './not-permitted';
@ -193,61 +193,32 @@ const LazyWithdrawals = React.lazy(
)
);
const routerConfig = [
const redirects = [
{
path: Routes.HOME,
// Not lazy as loaded when a user first hits the site
element: <Home name="Home" />,
},
{
path: Routes.TRANCHES,
element: <LazyTranches name="Tranches" />,
children: [
{ index: true, element: <LazyTranchesTranches /> },
{ path: ':trancheId', element: <LazyTranchesTranche /> },
],
},
{
path: Routes.CLAIM,
element: <LazyClaim name="Claim" />,
element: <Navigate to={Routes.PROPOSALS} replace />,
},
{
path: Routes.STAKING,
element: <LazyStaking name="Staking" />,
children: [
{ path: 'associate', element: <LazyStakingAssociate /> },
{ path: 'disassociate', element: <LazyStakingDisassociate /> },
{ path: ':node', element: <LazyStakingNode /> },
{
index: true,
element: <LazyStakingIndex />,
},
],
element: <Navigate to={Routes.VALIDATORS} replace />,
},
{
path: Routes.REWARDS,
element: <LazyRewards name="Rewards" />,
path: '/tranches',
element: <Navigate to={Routes.TRANCHES} replace />,
},
{
path: Routes.WITHDRAWALS,
element: <LazyWithdrawals name="Withdrawals" />,
path: '/withdrawals',
element: <Navigate to={Routes.WITHDRAWALS} replace />,
},
{
path: Routes.VESTING,
element: <LazyRedemption name="Vesting" />,
children: [
{
index: true,
element: <LazyRedemptionIndex />,
},
{
path: ':id',
element: <LazyRedemptionTranche />,
},
],
path: '/vesting',
element: <Navigate to={Routes.REDEEM} replace />,
},
];
const routerConfig = [
{
path: Routes.GOVERNANCE,
path: Routes.PROPOSALS,
element: <LazyGovernance name="Governance" />,
children: [
{ index: true, element: <LazyGovernanceProposals /> },
@ -281,6 +252,63 @@ const routerConfig = [
{ path: 'rejected', element: <LazyRejectedGovernanceProposals /> },
],
},
{
path: Routes.VALIDATORS,
element: <LazyStaking name="Staking" />,
children: [
{ path: 'associate', element: <LazyStakingAssociate /> },
{ path: 'disassociate', element: <LazyStakingDisassociate /> },
{ path: ':node', element: <LazyStakingNode /> },
{
index: true,
element: <LazyStakingIndex />,
},
],
},
{
path: Routes.REWARDS,
element: <LazyRewards name="Rewards" />,
},
{
path: Routes.TOKEN,
// Not lazy as loaded when a user first hits the site
children: [
{
element: <Home name="Home" />,
index: true,
},
{
path: Routes.TRANCHES,
element: <LazyTranches name="Tranches" />,
children: [
{ index: true, element: <LazyTranchesTranches /> },
{ path: ':trancheId', element: <LazyTranchesTranche /> },
],
},
{
path: Routes.WITHDRAWALS,
element: <LazyWithdrawals name="Withdrawals" />,
},
{
path: Routes.REDEEM,
element: <LazyRedemption name="Vesting" />,
children: [
{
index: true,
element: <LazyRedemptionIndex />,
},
{
path: ':id',
element: <LazyRedemptionTranche />,
},
],
},
],
},
{
path: Routes.CLAIM,
element: <LazyClaim name="Claim" />,
},
{
path: Routes.NOT_PERMITTED,
// Not lazy as loaded when a user first hits the site
@ -295,6 +323,7 @@ const routerConfig = [
// Not lazy as loaded when a user first hits the site
element: <NotFound name="NotFound" />,
},
...redirects,
];
export default routerConfig;

View File

@ -1,13 +1,16 @@
export default {
HOME: '/',
TRANCHES: '/tranches',
CLAIM: '/claim',
STAKING: '/staking',
VALIDATORS: '/validators',
REWARDS: '/rewards',
WITHDRAWALS: '/withdrawals',
GOVERNANCE: '/governance',
VESTING: '/vesting',
PROPOSALS: '/proposals',
NOT_PERMITTED: '/not-permitted',
NOT_FOUND: '/not-found',
CONTRACTS: '/contracts',
STAKING: '/staking',
TOKEN: '/token',
REDEEM: '/token/redeem',
WITHDRAWALS: '/token/withdraw',
TRANCHES: '/token/tranches',
ASSOCIATE: '/token/associate',
};

View File

@ -93,7 +93,7 @@ export const AssociateTransaction = ({
vegaKey: truncateMiddle(vegaKey),
})}
completeFooter={
<RouteLink to={Routes.STAKING}>
<RouteLink to={Routes.VALIDATORS}>
<Button>{t('Nominate Stake to Validator Node')}</Button>
</RouteLink>
}

View File

@ -39,7 +39,7 @@ export const DisassociateTransaction = ({
})
}
completeFooter={
<Link to={Routes.STAKING}>
<Link to={Routes.VALIDATORS}>
<Button>{t('backToStaking')}</Button>
</Link>
}

View File

@ -8,6 +8,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { createDocsLinks, ExternalLinks } from '@vegaprotocol/react-helpers';
import { useEnvironment } from '@vegaprotocol/environment';
import Routes from '../../../routes/routes';
export const StakingIntro = () => {
const { t } = useTranslation();
@ -24,7 +25,7 @@ export const StakingIntro = () => {
<li>
{t('stakingDescription1')}{' '}
<Link
to="/staking/associate"
to={Routes.ASSOCIATE}
className="underline"
data-testid="staking-associate-link"
>

View File

@ -9,8 +9,8 @@ import type { RouteChildProps } from '..';
const StakingRouter = ({ name }: RouteChildProps) => {
useDocumentTitle(name);
const { t } = useTranslation();
const associate = useMatch('/staking/associate');
const disassociate = useMatch('/staking/disassociate');
const associate = useMatch('/validators/associate');
const disassociate = useMatch('/validators/disassociate');
const title = React.useMemo(() => {
if (associate) {

View File

@ -44,7 +44,7 @@ export const StakeSuccess = ({
<div>
<p>{message}</p>
<p>
<Link className="underline" to={Routes.STAKING}>
<Link className="underline" to={Routes.VALIDATORS}>
{t('backToStaking')}
</Link>
</p>

View File

@ -88,7 +88,7 @@ export const VestingChart = () => {
stroke={colors.white}
strokeWidth={2}
label={{
position: 'right',
position: 'left',
value: currentDate,
fill: colors.white,
}}

View File

@ -7,8 +7,12 @@ import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
import { Vega } from '../icons/vega';
import type { HTMLAttributeAnchorTarget } from 'react';
import testnetBg from '../../assets/green-cloud.png';
import { Routes } from '../../pages/client-router';
import {
getNavLinkClassNames,
getActiveNavLinkClassNames,
Nav,
} from '@vegaprotocol/ui-toolkit';
type NavbarTheme = 'inherit' | 'dark' | 'yellow';
interface NavbarProps {
@ -27,61 +31,40 @@ export const Navbar = ({
marketId: store.marketId,
}));
const tradingPath = marketId ? `/markets/${marketId}` : '/markets';
const themeWrapperClasses = classNames({
dark: navbarTheme === 'dark',
});
const isYellow = navbarTheme === 'yellow';
const navbarClasses = classNames(
'flex items-stretch border-b px-4 border-default',
{
'dark:bg-black dark:text-white': !isYellow,
'bg-vega-yellow text-black bg-right-top bg-no-repeat bg-contain':
isYellow,
}
);
return (
<div className={themeWrapperClasses}>
<div
className={navbarClasses}
style={{
backgroundImage: isYellow ? `url("${testnetBg.src}")` : '',
}}
>
<div className="flex gap-4 items-center">
<Link to="/">
<Vega className="w-13" />
</Link>
<NetworkSwitcher />
</div>
<nav className="flex items-center flex-1 px-2">
<AppNavLink
name={t('Trading')}
path={tradingPath}
navbarTheme={navbarTheme}
/>
<AppNavLink
name={t('Portfolio')}
path={Routes.PORTFOLIO}
navbarTheme={navbarTheme}
/>
<a
href={`${VEGA_TOKEN_URL}/governance`}
target="_blank"
rel="noreferrer"
className={getActiveNavLinkClassNames(false, navbarTheme, true)}
>
{t('Governance')}
</a>
</nav>
<div className="flex items-center gap-2 ml-auto">
<VegaWalletConnectButton />
<ThemeSwitcher theme={theme} onToggle={toggleTheme} />
</div>
<Nav
navbarTheme={navbarTheme}
title={t('Console')}
titleContent={<NetworkSwitcher />}
icon={
<Link to="/">
<Vega className="w-13" />
</Link>
}
>
<AppNavLink
name={t('Trading')}
path={tradingPath}
navbarTheme={navbarTheme}
/>
<AppNavLink
name={t('Portfolio')}
path={Routes.PORTFOLIO}
navbarTheme={navbarTheme}
/>
<div className="flex items-center gap-2 ml-auto">
<a
href={`${VEGA_TOKEN_URL}/governance`}
target="_blank"
rel="noreferrer"
className={getActiveNavLinkClassNames(false, navbarTheme, true)}
>
{t('Governance')}
</a>
<VegaWalletConnectButton />
<ThemeSwitcher theme={theme} onToggle={toggleTheme} />
</div>
</div>
</Nav>
);
};
@ -124,28 +107,3 @@ const AppNavLink = ({
</NavLink>
);
};
function getNavLinkClassNames(
navbarTheme: string,
alignRight = false
): (props: { isActive?: boolean }) => string | undefined {
return ({ isActive = false }) => {
return getActiveNavLinkClassNames(isActive, navbarTheme, alignRight);
};
}
const getActiveNavLinkClassNames = (
isActive: boolean,
navbarTheme: string,
alignRight = false
): string | undefined => {
return classNames('mx-2 py-3 self-end relative', {
'cursor-default': isActive,
'text-black dark:text-white': isActive && navbarTheme !== 'yellow',
'text-neutral-500 dark:text-neutral-400 hover:text-black dark:hover:text-neutral-300':
!isActive && navbarTheme !== 'yellow',
'ml-auto': alignRight,
'text-black': isActive && navbarTheme === 'yellow',
'text-black/60 hover:text-black': !isActive && navbarTheme === 'yellow',
});
};

View File

@ -18,6 +18,7 @@ export * from './key-value-table';
export * from './link';
export * from './loader';
export * from './lozenge';
export * from './nav';
export * from './popover';
export * from './price-change';
export * from './progress-bar';

View File

@ -0,0 +1 @@
export * from './nav';

View File

@ -0,0 +1,19 @@
import { render } from '@testing-library/react';
import { Nav } from './nav';
describe('Nav', () => {
it('should render title, title content, icon and children', () => {
const { baseElement } = render(
<Nav
navbarTheme="dark"
icon={<div data-testid="icon" />}
titleContent={<div data-testid="title-content" />}
title={'Some title'}
>
<div data-testid="link"></div>
</Nav>
);
expect(baseElement).toBeTruthy();
});
});

View File

@ -0,0 +1,34 @@
import type { Meta, Story } from '@storybook/react';
import { ButtonLink } from '../button';
import { VegaLogo } from '../vega-logo';
import { Nav } from './nav';
export default {
component: Nav,
title: 'Nav',
} as Meta;
const Template: Story = ({ icon, title, titleContent }) => (
<div className="flex w-full">
<Nav icon={icon} title={title} titleContent={titleContent}>
<div className="mx-4">
<ButtonLink>Some link</ButtonLink>{' '}
</div>
<div className="mx-4">
<ButtonLink>Some other link</ButtonLink>
</div>
</Nav>
</div>
);
const props = {
icon: <VegaLogo />,
title: 'Title',
titleContent: <div>Content next to title</div>,
};
export const Default = Template.bind({});
Default.args = {
...props,
};

View File

@ -0,0 +1,79 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
export function getNavLinkClassNames(
navbarTheme: string,
alignRight = false
): (props: { isActive?: boolean }) => string | undefined {
return ({ isActive = false }) => {
return getActiveNavLinkClassNames(isActive, navbarTheme, alignRight);
};
}
export const getActiveNavLinkClassNames = (
isActive: boolean,
navbarTheme: string,
alignRight = false
): string | undefined => {
return classNames('mx-2 py-3 self-end relative', {
'cursor-default': isActive,
'text-black dark:text-white': isActive && navbarTheme !== 'yellow',
'text-neutral-500 dark:text-neutral-400 hover:text-black dark:hover:text-neutral-300':
!isActive && navbarTheme !== 'yellow',
'ml-auto': alignRight,
'text-black': isActive && navbarTheme === 'yellow',
'text-black/60 hover:text-black': !isActive && navbarTheme === 'yellow',
});
};
type NavbarTheme = 'inherit' | 'dark' | 'yellow';
interface NavbarProps {
navbarTheme?: NavbarTheme;
icon: ReactNode;
titleContent: ReactNode;
children: ReactNode;
title: string;
}
export const Nav = ({
navbarTheme = 'inherit',
children,
icon,
titleContent,
title,
}: NavbarProps) => {
const themeWrapperClasses = classNames('w-full', {
dark: navbarTheme === 'dark',
});
const isYellow = navbarTheme === 'yellow';
const navbarClasses = classNames(
'flex items-stretch border-b px-4 border-default',
{
'dark:bg-black dark:text-white': !isYellow,
'bg-vega-yellow text-black bg-right-top bg-no-repeat bg-contain':
isYellow,
}
);
return (
<div className={themeWrapperClasses}>
<div className={navbarClasses}>
<div className="flex gap-4 items-center">
{icon}
<h1
className={classNames(
'h-full flex flex-col my-0 justify-center font-alpha calt uppercase',
{ 'text-black': isYellow, 'text-white': !isYellow }
)}
>
{title}
</h1>
{titleContent}
</div>
{children}
</div>
</div>
);
};