Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2666ffe462 |
@@ -1,27 +1,33 @@
|
||||
context('Blocks page', { tags: '@regression' }, function () {
|
||||
before('visit token home page', function () {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
beforeEach(() => {
|
||||
cy.visit('/blocks');
|
||||
});
|
||||
const blockNavigation = 'a[href="/blocks"]';
|
||||
const blockHeight = '[data-testid="block-height"]';
|
||||
const blockTime = '[data-testid="block-time"]';
|
||||
const blockHeader = '[data-testid="block-header"]';
|
||||
const previousBlockBtn = '[data-testid="previous-block"]';
|
||||
const infiniteScrollWrapper = '[data-testid="infinite-scroll-wrapper"]';
|
||||
|
||||
beforeEach('navigate to blocks page', function () {
|
||||
cy.get(blockNavigation).click();
|
||||
});
|
||||
|
||||
it('Blocks page is displayed', function () {
|
||||
validateBlocksDisplayed();
|
||||
});
|
||||
|
||||
it('Blocks page is displayed on mobile', function () {
|
||||
cy.switchToMobile();
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(blockNavigation).click();
|
||||
validateBlocksDisplayed();
|
||||
});
|
||||
|
||||
it('Block validator page is displayed', function () {
|
||||
waitForBlocksResponse();
|
||||
cy.get(blockHeight).eq(0).find('a').click({ force: true });
|
||||
|
||||
cy.get(blockHeight).eq(0).click();
|
||||
cy.get('[data-testid="block-validator"]').should('not.be.empty');
|
||||
cy.get(blockTime).should('not.be.empty');
|
||||
//TODO: Add assertion for transactions when txs are added
|
||||
@@ -29,7 +35,7 @@ context('Blocks page', { tags: '@regression' }, function () {
|
||||
|
||||
it('Navigate to previous block', function () {
|
||||
waitForBlocksResponse();
|
||||
cy.get(blockHeight).eq(0).find('a').click({ force: true });
|
||||
cy.get(blockHeight).eq(0).click();
|
||||
cy.get(blockHeader)
|
||||
.invoke('text')
|
||||
.then(($blockHeaderTxt) => {
|
||||
|
||||
@@ -2,14 +2,17 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
before('navigate to network parameter page', function () {
|
||||
cy.fixture('net_parameter_format_lookup').as('networkParameterFormat');
|
||||
});
|
||||
describe('Verify elements on page', function () {
|
||||
beforeEach(() => {
|
||||
cy.visit('/network-parameters');
|
||||
});
|
||||
|
||||
describe('Verify elements on page', function () {
|
||||
const networkParametersNavigation = 'a[href="/network-parameters"]';
|
||||
const networkParametersHeader = '[data-testid="network-param-header"]';
|
||||
const tableRows = '[data-testid="key-value-table-row"]';
|
||||
|
||||
before('navigate to network parameter page', function () {
|
||||
cy.visit('/');
|
||||
cy.get(networkParametersNavigation).click();
|
||||
});
|
||||
|
||||
it('should show network parameter heading at top of page', function () {
|
||||
cy.get(networkParametersHeader)
|
||||
.should('have.text', 'Network Parameters')
|
||||
@@ -198,8 +201,55 @@ context('Network parameters page', { tags: '@smoke' }, function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see network parameters - on mobile', function () {
|
||||
cy.switchToMobile();
|
||||
it('should be able to switch network parameter page - between light and dark mode', function () {
|
||||
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
|
||||
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
|
||||
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
|
||||
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
|
||||
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
|
||||
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
|
||||
const themeSwitcher = '[data-testid="theme-switcher"]';
|
||||
const jsonFields = '.hljs';
|
||||
const sideMenuBackground = '.absolute';
|
||||
|
||||
// Engage dark mode if not already set
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.then((background_color) => {
|
||||
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
|
||||
cy.get(themeSwitcher).click();
|
||||
});
|
||||
|
||||
// Engage white mode
|
||||
cy.get(themeSwitcher).click();
|
||||
|
||||
// White Mode
|
||||
cy.get(networkParametersNavigation)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', whiteThemeSideMenuBackgroundColor);
|
||||
|
||||
// Dark Mode
|
||||
cy.get(themeSwitcher).click();
|
||||
cy.get(networkParametersNavigation)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSelectedMenuOptionColor);
|
||||
cy.get(jsonFields)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeJsonFieldBackColor);
|
||||
cy.get(sideMenuBackground)
|
||||
.should('have.css', 'background-color')
|
||||
.and('include', darkThemeSideMenuBackgroundColor);
|
||||
});
|
||||
|
||||
it.skip('should be able to see network parameters - on mobile', function () {
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.get(networkParametersNavigation).click();
|
||||
cy.get_network_parameters().then((network_parameters) => {
|
||||
network_parameters = Object.entries(network_parameters);
|
||||
network_parameters.forEach((network_parameter) => {
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import classnames from 'classnames';
|
||||
import { NetworkLoader, useInitializeEnv } from '@vegaprotocol/environment';
|
||||
import { Nav } from './components/nav';
|
||||
import { Header } from './components/header';
|
||||
import { Main } from './components/main';
|
||||
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
|
||||
import { Footer } from './components/footer/footer';
|
||||
import {
|
||||
AnnouncementBanner,
|
||||
ExternalLink,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { AnnouncementBanner, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
AssetDetailsDialog,
|
||||
useAssetDetailsDialogStore,
|
||||
} from '@vegaprotocol/assets';
|
||||
import { DEFAULT_CACHE_CONFIG } from '@vegaprotocol/apollo-client';
|
||||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
|
||||
const DialogsContainer = () => {
|
||||
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
|
||||
@@ -29,58 +25,35 @@ const DialogsContainer = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const MainnetSimAd = () => {
|
||||
const [shouldDisplayBanner, setShouldDisplayBanner] = useState<boolean>(true);
|
||||
|
||||
// Return an empty div so that the grid layout in _app.page.ts
|
||||
// renders correctly
|
||||
if (!shouldDisplayBanner) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AnnouncementBanner>
|
||||
<div className="grid grid-cols-[auto_1fr] gap-4 font-alpha calt uppercase text-center text-lg text-white">
|
||||
<button
|
||||
className="flex items-center"
|
||||
onClick={() => setShouldDisplayBanner(false)}
|
||||
>
|
||||
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
|
||||
</button>
|
||||
<div>
|
||||
<span className="pr-4">Mainnet sim 3 is coming soon!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">Learn more</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
);
|
||||
};
|
||||
|
||||
function App() {
|
||||
const layoutClasses = classnames(
|
||||
'grid grid-rows-[auto_1fr_auto] grid-cols-[1fr] md:grid-rows-[auto_minmax(700px,_1fr)_auto] md:grid-cols-[300px_1fr]',
|
||||
'min-h-[100vh] mx-auto my-0',
|
||||
'border-neutral-700 dark:border-neutral-300 lg:border-l lg:border-r',
|
||||
'bg-white dark:bg-black',
|
||||
'antialiased text-black dark:text-white',
|
||||
'overflow-hidden relative'
|
||||
);
|
||||
|
||||
return (
|
||||
<TendermintWebsocketProvider>
|
||||
<NetworkLoader cache={DEFAULT_CACHE_CONFIG}>
|
||||
<div
|
||||
className={classNames(
|
||||
'max-w-[1500px] min-h-[100vh]',
|
||||
'mx-auto my-0',
|
||||
'grid grid-rows-[auto_1fr_auto] grid-cols-1',
|
||||
'border-vega-light-200 dark:border-vega-dark-200 lg:border-l lg:border-r',
|
||||
'antialiased text-black dark:text-white',
|
||||
'overflow-hidden relative'
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<Header />
|
||||
<MainnetSimAd />
|
||||
</div>
|
||||
<div>
|
||||
<Main />
|
||||
</div>
|
||||
<div>
|
||||
<Footer />
|
||||
<AnnouncementBanner>
|
||||
<div className="font-alpha calt uppercase text-center text-lg text-white">
|
||||
<span className="pr-4">Mainnet sim 2 is live!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">
|
||||
Come help stress test the network
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
|
||||
<div className={layoutClasses}>
|
||||
<Header />
|
||||
<Nav />
|
||||
<Main />
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
<DialogsContainer />
|
||||
</NetworkLoader>
|
||||
</TendermintWebsocketProvider>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const Footer = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
|
||||
<footer className="grid grid-rows-2 grid-cols-[1fr_auto] text-xs md:text-md md:flex md:col-span-2 px-4 py-2 gap-4 border-t border-neutral-700 dark:border-neutral-300">
|
||||
<div className="flex justify-between gap-2 align-middle">
|
||||
{GIT_COMMIT_HASH && (
|
||||
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
|
||||
|
||||
@@ -19,10 +19,12 @@ const renderComponent = () => (
|
||||
);
|
||||
|
||||
describe('Header', () => {
|
||||
it('should render navigation', () => {
|
||||
it('should render heading', () => {
|
||||
render(renderComponent());
|
||||
|
||||
expect(screen.getByTestId('navigation')).toHaveTextContent('Explorer');
|
||||
expect(screen.getByTestId('explorer-header')).toHaveTextContent(
|
||||
'Vega Explorer'
|
||||
);
|
||||
});
|
||||
it('should render search', () => {
|
||||
render(renderComponent());
|
||||
|
||||
@@ -1,108 +1,43 @@
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
ThemeSwitcher,
|
||||
Navigation,
|
||||
NavigationList,
|
||||
NavigationItem,
|
||||
NavigationLink,
|
||||
NavigationBreakpoint,
|
||||
NavigationTrigger,
|
||||
NavigationContent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ThemeSwitcher, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Search } from '../search';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import type { Navigable } from '../../routes/router-config';
|
||||
import routerConfig from '../../routes/router-config';
|
||||
import { useMemo } from 'react';
|
||||
import compact from 'lodash/compact';
|
||||
import { Search } from '../search';
|
||||
|
||||
const routeToNavigationItem = (r: Navigable) => (
|
||||
<NavigationItem key={r.name}>
|
||||
<NavigationLink to={r.path}>{r.text}</NavigationLink>
|
||||
</NavigationItem>
|
||||
);
|
||||
import { useNavStore } from '../nav';
|
||||
|
||||
export const Header = () => {
|
||||
const mainItems = compact(
|
||||
[Routes.TX, Routes.BLOCKS, Routes.ORACLES, Routes.VALIDATORS].map((n) =>
|
||||
routerConfig.find((r) => r.path === n)
|
||||
)
|
||||
const [open, toggle] = useNavStore((state) => [state.open, state.toggle]);
|
||||
const headerClasses = classnames(
|
||||
'md:col-span-2',
|
||||
'grid grid-rows-2 md:grid-rows-1 grid-cols-[1fr_auto] md:grid-cols-[auto_1fr_auto] items-center',
|
||||
'p-4 gap-2 md:gap-4',
|
||||
'border-b border-neutral-700 dark:border-neutral-300 bg-black',
|
||||
'dark text-white'
|
||||
);
|
||||
|
||||
const groupedItems = compact(
|
||||
[
|
||||
Routes.PARTIES,
|
||||
Routes.ASSETS,
|
||||
Routes.MARKETS,
|
||||
Routes.GOVERNANCE,
|
||||
Routes.NETWORK_PARAMETERS,
|
||||
Routes.GENESIS,
|
||||
].map((n) => routerConfig.find((r) => r.path === n))
|
||||
);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
/**
|
||||
* Because the grouped items are displayed in a sub menu under an "Other" item
|
||||
* we need to determine whether any underlying item is active to highlight the
|
||||
* trigger in the same fashion as any other top-level `NavigationLink`.
|
||||
* This function checks whether the current location pathname is one of the
|
||||
* underlying NavigationLinks.
|
||||
*/
|
||||
const isOnOther = useMemo(() => {
|
||||
for (const path of groupedItems.map((r) => r.path)) {
|
||||
const matched = matchPath(`${path}/*`, pathname);
|
||||
if (matched) return true;
|
||||
}
|
||||
return false;
|
||||
}, [groupedItems, pathname]);
|
||||
|
||||
return (
|
||||
<Navigation
|
||||
appName="Explorer"
|
||||
theme="system"
|
||||
breakpoints={[490, 900]}
|
||||
actions={
|
||||
<>
|
||||
<ThemeSwitcher />
|
||||
<Search />
|
||||
</>
|
||||
}
|
||||
onResize={(width, el) => {
|
||||
if (width < 1157) {
|
||||
// switch to magnifying glass trigger when width < 1157
|
||||
el.classList.remove('nav-search-full');
|
||||
el.classList.add('nav-search-compact');
|
||||
} else {
|
||||
el.classList.remove('nav-search-compact');
|
||||
el.classList.add('nav-search-full');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<NavigationList hide={[NavigationBreakpoint.Small]}>
|
||||
<NavigationItem>
|
||||
<NetworkSwitcher />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Small, NavigationBreakpoint.Narrow]}
|
||||
<header className={headerClasses}>
|
||||
<div className="flex h-full items-center sm:items-stretch gap-4">
|
||||
<Link to={Routes.HOME}>
|
||||
<h1
|
||||
className="text-white text-3xl font-alpha uppercase calt mb-0"
|
||||
data-testid="explorer-header"
|
||||
>
|
||||
{t('Vega Explorer')}
|
||||
</h1>
|
||||
</Link>
|
||||
<NetworkSwitcher />
|
||||
</div>
|
||||
<button
|
||||
data-testid="open-menu"
|
||||
className="md:hidden text-white"
|
||||
onClick={() => toggle()}
|
||||
>
|
||||
{mainItems.map(routeToNavigationItem)}
|
||||
{groupedItems && (
|
||||
<NavigationItem>
|
||||
<NavigationTrigger isActive={Boolean(isOnOther)}>
|
||||
{t('Other')}
|
||||
</NavigationTrigger>
|
||||
<NavigationContent>
|
||||
<NavigationList>
|
||||
{groupedItems.map(routeToNavigationItem)}
|
||||
</NavigationList>
|
||||
</NavigationContent>
|
||||
</NavigationItem>
|
||||
)}
|
||||
</NavigationList>
|
||||
</Navigation>
|
||||
<Icon name={open ? 'cross' : 'menu'} />
|
||||
</button>
|
||||
<Search />
|
||||
<ThemeSwitcher className="-my-4" />
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './nav';
|
||||
@@ -0,0 +1,181 @@
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import type { Navigable } from '../../routes/router-config';
|
||||
import routerConfig from '../../routes/router-config';
|
||||
import classnames from 'classnames';
|
||||
import { create } from 'zustand';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import first from 'lodash/first';
|
||||
import last from 'lodash/last';
|
||||
import { BREAKPOINT_MD } from '../../config/breakpoints';
|
||||
|
||||
type NavStore = {
|
||||
open: boolean;
|
||||
toggle: () => void;
|
||||
hide: () => void;
|
||||
};
|
||||
|
||||
export const useNavStore = create<NavStore>((set, get) => ({
|
||||
open: false,
|
||||
toggle: () => set({ open: !get().open }),
|
||||
hide: () => set({ open: false }),
|
||||
}));
|
||||
|
||||
const NavLinks = ({ links }: { links: Navigable[] }) => {
|
||||
const navLinks = links.map((r) => (
|
||||
<li key={r.name}>
|
||||
<NavLink
|
||||
to={r.path}
|
||||
className={({ isActive }) =>
|
||||
classnames(
|
||||
'block mb-2 px-2',
|
||||
'text-lg hover:bg-vega-pink dark:hover:bg-vega-yellow hover:text-white dark:hover:text-black',
|
||||
{
|
||||
'bg-vega-pink text-white dark:bg-vega-yellow dark:text-black':
|
||||
isActive,
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
{r.text}
|
||||
</NavLink>
|
||||
</li>
|
||||
));
|
||||
|
||||
return <ul className="pr-8 md:pr-0">{navLinks}</ul>;
|
||||
};
|
||||
|
||||
export const Nav = () => {
|
||||
const [open, hide] = useNavStore((state) => [state.open, state.hide]);
|
||||
const location = useLocation();
|
||||
|
||||
const navRef = useRef<HTMLElement>(null);
|
||||
const btnRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const focusable = useMemo(
|
||||
() =>
|
||||
navRef.current
|
||||
? [
|
||||
...(navRef.current.querySelectorAll(
|
||||
'a, button'
|
||||
) as NodeListOf<HTMLElement>),
|
||||
]
|
||||
: [],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[navRef.current] // do not remove `navRef.current` from deps
|
||||
);
|
||||
|
||||
const closeNav = useCallback(() => {
|
||||
hide();
|
||||
console.log(focusable);
|
||||
focusable.forEach((fe) =>
|
||||
fe.setAttribute(
|
||||
'tabindex',
|
||||
window.innerWidth > BREAKPOINT_MD ? '0' : '-1'
|
||||
)
|
||||
);
|
||||
}, [focusable, hide]);
|
||||
|
||||
// close navigation when location changes
|
||||
useEffect(() => {
|
||||
closeNav();
|
||||
}, [closeNav, location]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (open) {
|
||||
focusable.forEach((fe) => fe.setAttribute('tabindex', '0'));
|
||||
}
|
||||
|
||||
document.body.style.overflow = open ? 'hidden' : '';
|
||||
const offset =
|
||||
document.querySelector('header')?.getBoundingClientRect().top || 0;
|
||||
if (navRef.current) {
|
||||
navRef.current.style.height = `calc(100vh - ${offset}px)`;
|
||||
}
|
||||
|
||||
// focus current by default
|
||||
if (navRef.current && open) {
|
||||
(navRef.current.querySelector('a[aria-current]') as HTMLElement)?.focus();
|
||||
}
|
||||
|
||||
const closeOnEsc = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeNav();
|
||||
}
|
||||
};
|
||||
|
||||
// tabbing loop
|
||||
const focusLast = (e: FocusEvent) => {
|
||||
e.preventDefault();
|
||||
const isNavElement =
|
||||
e.relatedTarget && navRef.current?.contains(e.relatedTarget as Node);
|
||||
if (!isNavElement && open) {
|
||||
last(focusable)?.focus();
|
||||
}
|
||||
};
|
||||
const focusFirst = (e: FocusEvent) => {
|
||||
e.preventDefault();
|
||||
const isNavElement =
|
||||
e.relatedTarget && navRef.current?.contains(e.relatedTarget as Node);
|
||||
if (!isNavElement && open) {
|
||||
first(focusable)?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const resetOnDesktop = () => {
|
||||
focusable.forEach((fe) =>
|
||||
fe.setAttribute(
|
||||
'tabindex',
|
||||
window.innerWidth > BREAKPOINT_MD ? '0' : '-1'
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', resetOnDesktop);
|
||||
|
||||
first(focusable)?.addEventListener('focusout', focusLast);
|
||||
last(focusable)?.addEventListener('focusout', focusFirst);
|
||||
|
||||
document.addEventListener('keydown', closeOnEsc);
|
||||
return () => {
|
||||
window.removeEventListener('resize', resetOnDesktop);
|
||||
document.removeEventListener('keydown', closeOnEsc);
|
||||
first(focusable)?.removeEventListener('focusout', focusLast);
|
||||
last(focusable)?.removeEventListener('focusout', focusFirst);
|
||||
};
|
||||
}, [closeNav, focusable, open]);
|
||||
|
||||
return (
|
||||
<nav
|
||||
ref={navRef}
|
||||
className={classnames(
|
||||
'absolute top-0 z-20 overflow-y-auto',
|
||||
'transition-[right]',
|
||||
{
|
||||
'right-[-200vw] h-full': !open,
|
||||
'right-0 h-[100vh]': open,
|
||||
},
|
||||
'w-full p-4 border-neutral-700 dark:border-neutral-300',
|
||||
'bg-white dark:bg-black',
|
||||
'md:static md:border-r'
|
||||
)}
|
||||
>
|
||||
<NavLinks links={routerConfig} />
|
||||
<button
|
||||
ref={btnRef}
|
||||
className="absolute top-0 right-0 p-4 md:hidden"
|
||||
onClick={() => {
|
||||
closeNav();
|
||||
}}
|
||||
>
|
||||
<Icon name="cross" />
|
||||
</button>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
interface CancelSummaryProps {
|
||||
orderId?: string;
|
||||
marketId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple component for rendering a reasonable string from an order cancellation
|
||||
*/
|
||||
export const CancelSummary = ({ orderId, marketId }: CancelSummaryProps) => {
|
||||
return <span className="font-bold">{getLabel(orderId, marketId)}</span>;
|
||||
};
|
||||
|
||||
export function getLabel(
|
||||
orderId: string | undefined,
|
||||
marketId: string | undefined
|
||||
): string {
|
||||
if (!orderId && !marketId) {
|
||||
return t('All orders');
|
||||
} else if (marketId && !orderId) {
|
||||
return t('All in market');
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
@@ -5,31 +5,11 @@ import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getSearchType, SearchTypes, toHex } from './detect-search';
|
||||
import { Routes } from '../../routes/route-names';
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface FormFields {
|
||||
search: string;
|
||||
}
|
||||
|
||||
const MagnifyingGlass = () => (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1="12.8202"
|
||||
y1="13.1798"
|
||||
x2="17.0629"
|
||||
y2="17.4224"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
<circle cx="8" cy="8" r="7.5" stroke="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Search = () => {
|
||||
const { register, handleSubmit } = useForm<FormFields>();
|
||||
const navigate = useNavigate();
|
||||
@@ -69,95 +49,39 @@ export const Search = () => {
|
||||
[navigate]
|
||||
);
|
||||
|
||||
const searchForm = (
|
||||
<form className="block min-w-[290px]" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="flex relative items-stretch gap-2 text-xs">
|
||||
<label htmlFor="search" className="sr-only">
|
||||
{t('Search by block number or transaction hash')}
|
||||
</label>
|
||||
<button
|
||||
className={classNames(
|
||||
'absolute top-[50%] translate-y-[-50%] left-2',
|
||||
'text-vega-light-300 dark:text-vega-dark-300'
|
||||
)}
|
||||
>
|
||||
<MagnifyingGlass />
|
||||
</button>
|
||||
<Input
|
||||
{...register('search')}
|
||||
id="search"
|
||||
data-testid="search"
|
||||
className={classNames(
|
||||
'peer',
|
||||
'pl-8 py-2 text-xs',
|
||||
'border rounded border-vega-light-200 dark:border-vega-dark-200'
|
||||
)}
|
||||
hasError={Boolean(error?.message)}
|
||||
type="text"
|
||||
placeholder={t('Enter block number, public key or transaction hash')}
|
||||
/>
|
||||
{error?.message && (
|
||||
<div
|
||||
className={classNames(
|
||||
'hidden peer-focus:block',
|
||||
'bg-white dark:bg-black',
|
||||
'border rounded-b border-t-0 border-vega-light-200 dark:border-vega-dark-200',
|
||||
'absolute top-[100%] flex-1 w-full pb-2 px-2 text-black dark:text-white'
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full md:max-w-[620px] justify-self-end"
|
||||
>
|
||||
<label htmlFor="search" className="sr-only">
|
||||
{t('Search by block number or transaction hash')}
|
||||
</label>
|
||||
<div className="flex items-stretch gap-2">
|
||||
<div className="flex grow relative">
|
||||
<Input
|
||||
{...register('search')}
|
||||
id="search"
|
||||
data-testid="search"
|
||||
className="text-white"
|
||||
hasError={Boolean(error?.message)}
|
||||
type="text"
|
||||
placeholder={t(
|
||||
'Enter block number, public key or transaction hash'
|
||||
)}
|
||||
>
|
||||
<InputError
|
||||
data-testid="search-error"
|
||||
intent="danger"
|
||||
className="text-xs"
|
||||
>
|
||||
{error.message}
|
||||
</InputError>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
className="hidden [.search-dropdown_&]:block"
|
||||
type="submit"
|
||||
size="xs"
|
||||
data-testid="search-button"
|
||||
>
|
||||
/>
|
||||
{error?.message && (
|
||||
<div className="bg-white border border-t-0 border-accent absolute top-[100%] flex-1 w-full pb-2 px-2 rounded-b text-black">
|
||||
<InputError data-testid="search-error" intent="danger">
|
||||
{error.message}
|
||||
</InputError>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button type="submit" size="sm" data-testid="search-button">
|
||||
{t('Search')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
const searchTrigger = (
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button className="text-vega-light-300 dark:text-vega-dark-300 data-open:text-black dark:data-open:text-white flex items-center">
|
||||
<MagnifyingGlass />
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className={classNames(
|
||||
'search-dropdown',
|
||||
'p-2 min-w-[290px] z-20',
|
||||
'text-vega-light-300 dark:text-vega-dark-300',
|
||||
'bg-white dark:bg-black',
|
||||
'border rounded border-vega-light-200 dark:border-vega-dark-200',
|
||||
'shadow-[8px_8px_16px_0_rgba(0,0,0,0.4)]'
|
||||
)}
|
||||
align="end"
|
||||
sideOffset={10}
|
||||
>
|
||||
{searchForm}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="hidden [.nav-search-full_&]:block">{searchForm}</div>
|
||||
<div className="hidden [.nav-search-compact_&]:block">
|
||||
{searchTrigger}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { BatchCancellationInstruction } from '../../../../routes/types/bloc
|
||||
import { TxOrderType } from '../../tx-order-type';
|
||||
import { MarketLink } from '../../../links';
|
||||
import OrderSummary from '../../../order-summary/order-summary';
|
||||
import { CancelSummary } from '../../../order-summary/order-cancellation';
|
||||
|
||||
interface BatchCancelProps {
|
||||
index: number;
|
||||
@@ -20,14 +19,7 @@ export const BatchCancel = ({ index, submission }: BatchCancelProps) => {
|
||||
<TxOrderType orderType={'OrderCancellation'} />
|
||||
</td>
|
||||
<td>
|
||||
{submission.orderId ? (
|
||||
<OrderSummary id={submission.orderId} modifier="cancelled" />
|
||||
) : (
|
||||
<CancelSummary
|
||||
orderId={submission.orderId}
|
||||
marketId={submission.marketId}
|
||||
/>
|
||||
)}
|
||||
<OrderSummary id={submission.orderId} modifier="cancelled" />
|
||||
</td>
|
||||
<td>
|
||||
<MarketLink id={submission.marketId} />
|
||||
|
||||
@@ -5,8 +5,6 @@ import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint
|
||||
import { TxDetailsShared } from './shared/tx-details-shared';
|
||||
import { TableCell, TableRow, TableWithTbody } from '../../table';
|
||||
import DeterministicOrderDetails from '../../order-details/deterministic-order-details';
|
||||
import { CancelSummary } from '../../order-summary/order-cancellation';
|
||||
import Hash from '../../links/hash';
|
||||
|
||||
interface TxDetailsOrderCancelProps {
|
||||
txData: BlockExplorerTransactionResult | undefined;
|
||||
@@ -26,8 +24,8 @@ export const TxDetailsOrderCancel = ({
|
||||
return <>{t('Awaiting Block Explorer transaction details')}</>;
|
||||
}
|
||||
|
||||
const marketId: string = txData.command.orderCancellation.marketId;
|
||||
const orderId: string = txData.command.orderCancellation.orderId;
|
||||
const marketId: string = txData.command.orderCancellation.marketId || '-';
|
||||
const orderId: string = txData.command.orderCancellation.orderId || '-';
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -40,24 +38,18 @@ export const TxDetailsOrderCancel = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Order')}</TableCell>
|
||||
<TableCell>
|
||||
{orderId ? (
|
||||
<Hash text={orderId} />
|
||||
) : (
|
||||
<CancelSummary orderId={orderId} marketId={marketId} />
|
||||
)}
|
||||
<code>{orderId}</code>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{marketId ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Market')}</TableCell>
|
||||
<TableCell>
|
||||
<MarketLink id={marketId} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
</TableWithTbody>
|
||||
|
||||
{orderId ? <DeterministicOrderDetails id={orderId} /> : null}
|
||||
{orderId !== '-' ? <DeterministicOrderDetails id={orderId} /> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,18 +23,14 @@ import { NetworkParameters } from './network-parameters';
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import { MarketPage, MarketsPage } from './markets';
|
||||
|
||||
export type Navigable = {
|
||||
path: string;
|
||||
name: string;
|
||||
text: string;
|
||||
};
|
||||
export type Navigable = { path: string; name: string; text: string };
|
||||
type Route = RouteObject & Navigable;
|
||||
|
||||
const partiesRoutes: Route[] = flags.parties
|
||||
? [
|
||||
{
|
||||
path: Routes.PARTIES,
|
||||
name: t('Parties'),
|
||||
name: 'Parties',
|
||||
text: t('Parties'),
|
||||
element: <Party />,
|
||||
children: [
|
||||
@@ -56,7 +52,7 @@ const assetsRoutes: Route[] = flags.assets
|
||||
{
|
||||
path: Routes.ASSETS,
|
||||
text: t('Assets'),
|
||||
name: t('Assets'),
|
||||
name: 'Assets',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
@@ -75,7 +71,7 @@ const genesisRoutes: Route[] = flags.genesis
|
||||
? [
|
||||
{
|
||||
path: Routes.GENESIS,
|
||||
name: t('Genesis'),
|
||||
name: 'Genesis',
|
||||
text: t('Genesis Parameters'),
|
||||
element: <Genesis />,
|
||||
},
|
||||
@@ -86,7 +82,7 @@ const governanceRoutes: Route[] = flags.governance
|
||||
? [
|
||||
{
|
||||
path: Routes.GOVERNANCE,
|
||||
name: t('Governance proposals'),
|
||||
name: 'Governance proposals',
|
||||
text: t('Governance Proposals'),
|
||||
element: <Proposals />,
|
||||
},
|
||||
@@ -97,7 +93,7 @@ const marketsRoutes: Route[] = flags.markets
|
||||
? [
|
||||
{
|
||||
path: Routes.MARKETS,
|
||||
name: t('Markets'),
|
||||
name: 'Markets',
|
||||
text: t('Markets'),
|
||||
children: [
|
||||
{
|
||||
@@ -117,18 +113,17 @@ const networkParametersRoutes: Route[] = flags.networkParameters
|
||||
? [
|
||||
{
|
||||
path: Routes.NETWORK_PARAMETERS,
|
||||
name: t('NetworkParameters'),
|
||||
name: 'NetworkParameters',
|
||||
text: t('Network Parameters'),
|
||||
element: <NetworkParameters />,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const validators: Route[] = flags.validators
|
||||
? [
|
||||
{
|
||||
path: Routes.VALIDATORS,
|
||||
name: t('Validators'),
|
||||
name: 'Validators',
|
||||
text: t('Validators'),
|
||||
element: <ValidatorsPage />,
|
||||
},
|
||||
@@ -138,14 +133,14 @@ const validators: Route[] = flags.validators
|
||||
const routerConfig: Route[] = [
|
||||
{
|
||||
path: Routes.HOME,
|
||||
name: t('Home'),
|
||||
name: 'Home',
|
||||
text: t('Home'),
|
||||
element: <Home />,
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
path: Routes.TX,
|
||||
name: t('Txs'),
|
||||
name: 'Txs',
|
||||
text: t('Transactions'),
|
||||
element: <Txs />,
|
||||
children: [
|
||||
@@ -165,7 +160,7 @@ const routerConfig: Route[] = [
|
||||
},
|
||||
{
|
||||
path: Routes.BLOCKS,
|
||||
name: t('Blocks'),
|
||||
name: 'Blocks',
|
||||
text: t('Blocks'),
|
||||
element: <BlockPage />,
|
||||
children: [
|
||||
@@ -181,7 +176,7 @@ const routerConfig: Route[] = [
|
||||
},
|
||||
{
|
||||
path: Routes.ORACLES,
|
||||
name: t('Oracles'),
|
||||
name: 'Oracles',
|
||||
text: t('Oracles'),
|
||||
element: <OraclePage />,
|
||||
children: [
|
||||
|
||||
@@ -3,13 +3,3 @@
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
Object.defineProperty(window, 'ResizeObserver', {
|
||||
writable: false,
|
||||
value: jest.fn().mockImplementation(() => ({
|
||||
observe: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
connect: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.BTC.value",
|
||||
"name": "prices.ETH.value",
|
||||
"type": "TYPE_INTEGER",
|
||||
"numberDecimalPlaces": "0"
|
||||
},
|
||||
@@ -38,34 +38,30 @@
|
||||
}
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementDataProperty": "prices.BTC.value",
|
||||
"settlementPriceProperty": "prices.BTC.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"lpPriceRange": "11",
|
||||
"instrument": {
|
||||
"code": "Token.24h",
|
||||
"future": {
|
||||
"quoteName": "fBTC",
|
||||
"dataSourceSpecForSettlementData": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.BTC.value",
|
||||
"type": "TYPE_INTEGER"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
|
||||
"value": "1648684800000000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementDataProperty": "prices.BTC.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
|
||||
"priceMonitoringParameters": {
|
||||
"triggers": [
|
||||
{
|
||||
"horizon": "43200",
|
||||
"probability": "0.9999999",
|
||||
"auctionExtension": "600"
|
||||
}
|
||||
]
|
||||
},
|
||||
"logNormal": {
|
||||
"tau": 0.0001140771161,
|
||||
"riskAversionParameter": 0.001,
|
||||
"params": {
|
||||
"mu": 0,
|
||||
"r": 0.016,
|
||||
"sigma": 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +1,62 @@
|
||||
{
|
||||
"lpPriceRange": "10",
|
||||
"instrument": {
|
||||
"code": "TEST.24h",
|
||||
"future": {
|
||||
"quoteName": "fUSDC",
|
||||
"settlementDataDecimals": 5,
|
||||
"dataSourceSpecForSettlementData": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.ETH.value",
|
||||
"type": "TYPE_INTEGER"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.ETH.value",
|
||||
"type": "TYPE_INTEGER",
|
||||
"numberDecimalPlaces": "0"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
"operator": "OPERATOR_GREATER_THAN_OR_EQUAL",
|
||||
"value": "1648684800000000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementDataProperty": "prices.ETH.value",
|
||||
"settlementPriceProperty": "prices.ETH.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": [
|
||||
"sector:energy",
|
||||
"sector:food",
|
||||
"source:docs.vega.xyz",
|
||||
"test:update"
|
||||
],
|
||||
"metadata": ["sector:energy", "sector:food", "source:docs.vega.xyz"],
|
||||
"priceMonitoringParameters": {
|
||||
"triggers": [
|
||||
{
|
||||
@@ -80,14 +66,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"liquidityMonitoringParameters": {
|
||||
"targetStakeParameters": {
|
||||
"timeWindow": "3600",
|
||||
"scalingFactor": 10
|
||||
},
|
||||
"triggeringRatio": "0.7",
|
||||
"auctionExtension": "1"
|
||||
},
|
||||
"logNormal": {
|
||||
"tau": 0.0001140771161,
|
||||
"riskAversionParameter": 0.001,
|
||||
|
||||
@@ -26,10 +26,6 @@ const enactmentDeadlineError =
|
||||
'[data-testid="enactment-before-voting-deadline"]';
|
||||
const proposalDownloadBtn = '[data-testid="proposal-download-json"]';
|
||||
const feedbackError = '[data-testid="Error"]';
|
||||
const viewProposalBtn = 'view-proposal-btn';
|
||||
const liquidityVoteStatus = 'liquidity-votes-status';
|
||||
const tokenVoteStatus = 'token-votes-status';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
const epochTimeout = Cypress.env('epochTimeout');
|
||||
const proposalTimeout = { timeout: 14000 };
|
||||
|
||||
@@ -49,7 +45,6 @@ context(
|
||||
{ tags: '@slow' },
|
||||
function () {
|
||||
before('connect wallets and set approval limit', function () {
|
||||
cy.createMarket();
|
||||
cy.visit('/');
|
||||
cy.vega_wallet_set_specified_approval_amount('1000');
|
||||
});
|
||||
@@ -70,7 +65,7 @@ context(
|
||||
// 3002-PROP-007
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
|
||||
cy.get(proposalParameterSelect).find('option').should('have.length', 117);
|
||||
cy.get(proposalParameterSelect).find('option').should('have.length', 116);
|
||||
cy.get(proposalParameterSelect).select(
|
||||
// 3007-PNEC-002
|
||||
'governance_proposal_asset_minEnact'
|
||||
@@ -180,8 +175,9 @@ context(
|
||||
cy.get(enactmentDeadlineError).should('not.exist');
|
||||
});
|
||||
|
||||
// 3003-PMAN-001
|
||||
it('Able to submit valid new market proposal', function () {
|
||||
// Skipping because unclear what the required json is yet for new market proposal, will update once docs have been updated
|
||||
// 3003-todo-PMAN-001
|
||||
it.skip('Able to submit valid new market proposal', function () {
|
||||
cy.go_to_make_new_proposal(governanceProposalType.NEW_MARKET);
|
||||
cy.get(newProposalTitle).type('Test new market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
@@ -203,7 +199,6 @@ context(
|
||||
cy.get(newProposalTitle).type('Test new market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.fixture('/proposals/new-market').then((newMarketProposal) => {
|
||||
newMarketProposal.invalid = 'I am an invalid field';
|
||||
let newMarketPayload = JSON.stringify(newMarketProposal);
|
||||
cy.get(newProposalTerms).type(newMarketPayload, {
|
||||
parseSpecialCharSequences: false,
|
||||
@@ -214,66 +209,11 @@ context(
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should(
|
||||
'have.text',
|
||||
'Invalid params: the transaction is not a valid Vega command: unknown field "invalid" in vega.NewMarket'
|
||||
'Invalid params: the transaction is malformed'
|
||||
);
|
||||
});
|
||||
|
||||
// Will fail if run after 'Able to submit update market proposal and vote for proposal'
|
||||
// 3002-PROP-022
|
||||
it('Unable to submit update market proposal without equity-like share in the market', function () {
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal - rejected');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.get(proposalMarketSelect).select('Test market 1');
|
||||
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
|
||||
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
|
||||
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
|
||||
parseSpecialCharSequences: false,
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
|
||||
cy.getByTestId('dialog-content')
|
||||
.find('p')
|
||||
.should('have.text', 'PROPOSAL_ERROR_INSUFFICIENT_EQUITY_LIKE_SHARE');
|
||||
cy.ensure_specified_unstaked_tokens_are_associated('1');
|
||||
});
|
||||
|
||||
// 3002-PROP-020
|
||||
it('Unable to submit update market proposal without minimum amount of tokens', function () {
|
||||
cy.vega_wallet_teardown();
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
'fUSDC',
|
||||
'1000000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal - rejected');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
cy.get(proposalMarketSelect).select('Test market 1');
|
||||
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
|
||||
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
|
||||
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
|
||||
parseSpecialCharSequences: false,
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
cy.get(feedbackError).should(
|
||||
'have.text',
|
||||
'Network error: the network blocked the transaction through the spam protection: party has insufficient associated governance tokens in their staking account to submit proposal request (ABCI code 89)'
|
||||
);
|
||||
});
|
||||
|
||||
// 3001-VOTE-092
|
||||
it('Able to submit update market proposal and vote for proposal', function () {
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
'fUSDC',
|
||||
'1000000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
it.skip('Able to submit update market proposal', function () {
|
||||
cy.go_to_make_new_proposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
@@ -282,10 +222,6 @@ context(
|
||||
cy.get('dd').eq(0).should('have.text', 'Test market 1');
|
||||
cy.get('dd').eq(1).should('have.text', 'TEST.24h');
|
||||
cy.get('dd').eq(2).should('not.be.empty');
|
||||
cy.get('dd').eq(2).invoke('text').as('EnactedMarketId');
|
||||
});
|
||||
cy.get('@EnactedMarketId').then((marketId) => {
|
||||
cy.VegaWalletSubmitLiquidityProvision(marketId, '1');
|
||||
});
|
||||
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
|
||||
let newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
|
||||
@@ -296,34 +232,6 @@ context(
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.wait_for_proposal_submitted();
|
||||
cy.navigate_to('proposals');
|
||||
cy.get('@EnactedMarketId').then((marketId) => {
|
||||
cy.contains(marketId)
|
||||
.parentsUntil(proposalListItem)
|
||||
.within(() => {
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(liquidityVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to fail'
|
||||
);
|
||||
cy.getByTestId(tokenVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to fail'
|
||||
);
|
||||
cy.vote_for_proposal('for');
|
||||
cy.getByTestId(liquidityVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.getByTestId(tokenVoteStatus).should(
|
||||
'contain.text',
|
||||
'Currently expected to pass'
|
||||
);
|
||||
cy.get_proposal_information_from_table('Expected to pass')
|
||||
.contains('👍 by Token vote')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
// 3001-VOTE-026 3001-VOTE-027 3001-VOTE-028 3001-VOTE-095 3001-VOTE-096 3005-PASN-001
|
||||
@@ -390,7 +298,7 @@ context(
|
||||
.parentsUntil(proposalListItem)
|
||||
.within(() => {
|
||||
cy.get(proposalDetails).should('contain.text', assetId); // 3001-VOTE-029
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
cy.getByTestId('view-proposal-btn').click();
|
||||
});
|
||||
});
|
||||
cy.get_proposal_information_from_table('Proposed enactment') // 3001-VOTE-044
|
||||
|
||||
@@ -14,14 +14,13 @@ const stakeShare = '[data-testid="stake-percentage"]';
|
||||
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
|
||||
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
|
||||
const vegaWalletUnstakedBalance =
|
||||
'[data-testid="vega-wallet-balance-unstaked"]:visible';
|
||||
'[data-testid="vega-wallet-balance-unstaked"]';
|
||||
const vegaWalletStakedBalances =
|
||||
'[data-testid="vega-wallet-balance-staked-validators"]';
|
||||
const ethWalletAssociatedBalances =
|
||||
'[data-testid="eth-wallet-associated-balances"]';
|
||||
const ethWalletTotalAssociatedBalance =
|
||||
'[data-testid="currency-locked"]:visible';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]:visible';
|
||||
const ethWalletTotalAssociatedBalance = '[data-testid="currency-locked"]';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const vegaWallet = '[data-testid="vega-wallet"]';
|
||||
const partValidatorId = '…';
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
|
||||
@@ -1,10 +1,60 @@
|
||||
const navSection = 'nav';
|
||||
const navSupply = '[href="/token/tranches"]';
|
||||
const navToken = '[href="/token"]';
|
||||
const navStaking = '[href="/validators"]';
|
||||
const navRewards = '[href="/rewards"]';
|
||||
const navWithdraw = '[href="/token/withdraw"]';
|
||||
const navGovernance = '[href="/proposals"]';
|
||||
const navRedeem = '[href="/token/redeem"]';
|
||||
|
||||
context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
|
||||
before('visit token home page', function () {
|
||||
cy.visit('/');
|
||||
cy.get('nav', { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
before('wait for page to load', function () {
|
||||
cy.get(navSection, { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('Navigation tabs', function () {
|
||||
it('should have proposals tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navGovernance).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have validators tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navStaking).should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should have rewards tab', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.get(navRewards).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Token dropdown', function () {
|
||||
before('click on token dropdown', function () {
|
||||
cy.get(navSection).within(() => {
|
||||
cy.getByTestId('state-trigger').realClick();
|
||||
});
|
||||
});
|
||||
it('should have token dropdown', function () {
|
||||
cy.get(navToken).should('be.visible');
|
||||
});
|
||||
it('should have supply & vesting dropdown', function () {
|
||||
cy.get(navSupply).should('be.visible');
|
||||
});
|
||||
it('should have withdraw dropdown', function () {
|
||||
cy.get(navWithdraw).should('be.visible');
|
||||
});
|
||||
it('should have redeem dropdown', function () {
|
||||
cy.get(navRedeem).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Links and buttons', function () {
|
||||
it('should have link for proposal page', function () {
|
||||
cy.getByTestId('home-proposals').within(() => {
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
context(
|
||||
'Landing pages - verifies required elements',
|
||||
{ tags: '@smoke' },
|
||||
() => {
|
||||
const navbar = 'nav .navbar';
|
||||
const mobileNav = '[data-testid="menu-drawer"]';
|
||||
|
||||
const topLevelLinks = [
|
||||
{
|
||||
name: 'Proposals',
|
||||
selector: '[href="/proposals"]',
|
||||
tests: () => {
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
const governanceDocsUrl = 'https://vega.xyz/governance';
|
||||
const proposalDocumentationLink =
|
||||
'[data-testid="proposal-documentation-link"]';
|
||||
// 3001-VOTE-001
|
||||
cy.get(proposalDocumentationLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Find out more about Vega governance')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', governanceDocsUrl);
|
||||
|
||||
// 3002-PROP-001
|
||||
cy.request(governanceDocsUrl)
|
||||
.its('body')
|
||||
.then((body) => {
|
||||
if (!body.includes('Govern the network')) {
|
||||
assert.include(
|
||||
body,
|
||||
'Govern the network',
|
||||
`Checking that governance link destination includes 'Govern the network' text`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see button for - new proposal', function () {
|
||||
// 3001-VOTE-002
|
||||
const newProposalLink = '[data-testid="new-proposal-link"]';
|
||||
cy.get(newProposalLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'New proposal')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', '/proposals/propose');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Validators',
|
||||
selector: '[href="/validators"]',
|
||||
tests: () => {
|
||||
it('Should have Staking Guide link visible', function () {
|
||||
// 2001-STKE-003
|
||||
cy.get('[data-testid="staking-guide-link"]')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Read more about staking on Vega')
|
||||
.and(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://docs.vega.xyz/mainnet/concepts/vega-chain/#staking-on-vega'
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Rewards',
|
||||
selector: '[href="/rewards"]',
|
||||
header: 'Rewards and fees',
|
||||
tests: () => {
|
||||
it('should have epoch warning', () => {
|
||||
cy.get('[data-testid="callout"]')
|
||||
.should('be.visible')
|
||||
.and(
|
||||
'have.text',
|
||||
'Rewards are credited 5 minutes after the epoch ends.This delay is set by a network parameter'
|
||||
);
|
||||
});
|
||||
it('should have toggle for seeing total vs individual rewards', () => {
|
||||
cy.get('[data-testid="epoch-reward-view-toggle-total"]').should(
|
||||
'be.visible'
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const secondLevelLinks = [
|
||||
{
|
||||
trigger: true,
|
||||
name: 'Token',
|
||||
selector: '[data-testid="state-trigger"]',
|
||||
},
|
||||
{
|
||||
name: 'Token',
|
||||
selector: '[href="/token"]',
|
||||
header: 'The $VEGA token',
|
||||
},
|
||||
{
|
||||
name: 'Supply & Vesting',
|
||||
selector: '[href="/token/tranches"]',
|
||||
header: 'Vesting tranches',
|
||||
},
|
||||
{
|
||||
name: 'Withdraw',
|
||||
selector: '[href="/token/withdraw"]',
|
||||
header: 'Withdrawals',
|
||||
tests: () => {
|
||||
it('should have connect Vega wallet button', function () {
|
||||
cy.get('[data-testid="connect-to-vega-wallet-btn"]')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Redeem',
|
||||
selector: '[href="/token/redeem"]',
|
||||
header: 'Vesting',
|
||||
tests: () => {
|
||||
// 1005-VEST-018
|
||||
it('should have connect Eth wallet button', function () {
|
||||
cy.get('[data-testid="connect-to-eth-btn"]')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Ethereum wallet');
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Associate',
|
||||
selector: '[href="/token/associate"]',
|
||||
header: 'Associate $VEGA tokens with Vega Key',
|
||||
},
|
||||
{
|
||||
name: 'Disassociate',
|
||||
selector: '[href="/token/disassociate"]',
|
||||
header: 'Disassociate $VEGA tokens from a Vega key',
|
||||
},
|
||||
];
|
||||
|
||||
const expand = () => {
|
||||
const trigger = secondLevelLinks.find((l) => l.trigger).selector;
|
||||
cy.get(trigger).then((el) => {
|
||||
if (el.attr('aria-expanded') === 'false') {
|
||||
el.trigger('click');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const collapse = () => {
|
||||
const trigger = secondLevelLinks.find((l) => l.trigger).selector;
|
||||
cy.get(trigger).then((el) => {
|
||||
if (el.attr('aria-expanded') === 'true') {
|
||||
el.trigger('click');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const ensureHeader = (text) => {
|
||||
cy.get('main header h1').should('have.text', text);
|
||||
};
|
||||
|
||||
before(() => {
|
||||
// goes to HOME
|
||||
cy.visit('/');
|
||||
// and waits for it to load
|
||||
cy.get(navbar, { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('Navigation (desktop)', () => {
|
||||
for (const { name, selector } of topLevelLinks) {
|
||||
it(`should have ${name} nav link`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, selector, trigger } of secondLevelLinks) {
|
||||
it(`should have ${name} ${
|
||||
trigger ? 'as trigger button' : ''
|
||||
} second level nav link`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
if (trigger) cy.get(selector).click();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
after(() => {
|
||||
collapse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigation (mobile)', () => {
|
||||
beforeEach(() => {
|
||||
// iphone xr
|
||||
cy.viewport(414, 896);
|
||||
});
|
||||
|
||||
it('should have burger button', () => {
|
||||
cy.get('[data-testid="button-menu-drawer"]').should('be.visible');
|
||||
cy.get('[data-testid="button-menu-drawer"]').click();
|
||||
cy.get(mobileNav).should('be.visible');
|
||||
});
|
||||
|
||||
for (const { name, selector } of topLevelLinks) {
|
||||
it(`should have ${name} nav link`, () => {
|
||||
cy.get(mobileNav).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, selector, trigger } of secondLevelLinks) {
|
||||
it(`should have ${name} ${
|
||||
trigger ? 'as trigger button' : ''
|
||||
} second level nav link`, () => {
|
||||
cy.get(mobileNav).within(() => {
|
||||
cy.get(selector).should('be.visible');
|
||||
cy.get(selector).should('have.text', name);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
after(() => {
|
||||
cy.get('[data-testid="button-menu-drawer"]').click();
|
||||
cy.viewport(
|
||||
Cypress.config('viewportWidth'),
|
||||
Cypress.config('viewportHeight')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Elements', () => {
|
||||
for (const { name, selector, header, tests } of topLevelLinks) {
|
||||
describe(`${name} page`, () => {
|
||||
it(`navigates to ${name}`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
cy.log(`goes to ${name}`);
|
||||
cy.get(selector).click();
|
||||
cy.log(`ensures ${name} is highlighted`);
|
||||
cy.get(selector).should('have.attr', 'aria-current');
|
||||
});
|
||||
});
|
||||
it('displays header', () => {
|
||||
ensureHeader(header || name);
|
||||
});
|
||||
|
||||
if (tests) tests.apply(this);
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, selector, header, tests } of secondLevelLinks.filter(
|
||||
(l) => !l.trigger
|
||||
)) {
|
||||
describe(`${name} page`, () => {
|
||||
it(`navigates to ${name}`, () => {
|
||||
cy.get(navbar).within(() => {
|
||||
expand();
|
||||
cy.log(`goes to ${name}`);
|
||||
cy.get(selector).click();
|
||||
expand();
|
||||
cy.log(`ensures ${name} is highlighted`);
|
||||
cy.get(selector).should('have.attr', 'aria-current');
|
||||
});
|
||||
});
|
||||
it('displays header', () => {
|
||||
ensureHeader(header || name);
|
||||
});
|
||||
|
||||
if (tests) tests.apply(this);
|
||||
});
|
||||
}
|
||||
|
||||
after(() => {
|
||||
collapse();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,68 @@
|
||||
const proposalDocumentationLink = '[data-testid="proposal-documentation-link"]';
|
||||
const newProposalButton = '[data-testid="new-proposal-link"]';
|
||||
const newProposalLink = '[data-testid="new-proposal-link"]';
|
||||
const governanceDocsUrl = 'https://vega.xyz/governance';
|
||||
const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]';
|
||||
|
||||
context(
|
||||
'Governance Page - verify elements on page',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
before('navigate to governance page', function () {
|
||||
cy.visit('/').navigate_to('proposals');
|
||||
});
|
||||
|
||||
describe('with no network change proposals', function () {
|
||||
it('should have governance tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('proposals');
|
||||
});
|
||||
|
||||
it('should have GOVERNANCE header visible', function () {
|
||||
cy.verify_page_header('Proposals');
|
||||
});
|
||||
|
||||
it('should be able to see a working link for - find out more about Vega governance', function () {
|
||||
// 3001-VOTE-001
|
||||
cy.get(proposalDocumentationLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Find out more about Vega governance')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', governanceDocsUrl);
|
||||
|
||||
// 3002-PROP-001
|
||||
cy.request(governanceDocsUrl)
|
||||
.its('body')
|
||||
.then((body) => {
|
||||
if (!body.includes('Govern the network')) {
|
||||
assert.include(
|
||||
body,
|
||||
'Govern the network',
|
||||
`Checking that governance link destination includes 'Govern the network' text`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to see button for - new proposal', function () {
|
||||
// 3001-VOTE-002
|
||||
cy.get(newProposalLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'New proposal')
|
||||
.and('have.attr', 'href')
|
||||
.and('equal', '/proposals/propose');
|
||||
});
|
||||
|
||||
// Skipping this test for now, the new proposal button no longer takes a user directly
|
||||
// to a proposal form, instead it takes them to a page where they can select a proposal type.
|
||||
// Keeping this test here for now as it can be repurposed to test the new proposal forms.
|
||||
it.skip('should be able to see a connect wallet button - if vega wallet disconnected and new proposal button selected', function () {
|
||||
cy.get(newProposalButton).should('be.visible').click();
|
||||
cy.get(connectToVegaWalletButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
cy.navigate_to('proposals');
|
||||
cy.wait_for_spinner();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -50,7 +50,7 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
});
|
||||
|
||||
it('Able to disconnect via wallet', function () {
|
||||
cy.get('aside [data-testid="manage-vega-wallet"]').click();
|
||||
cy.getByTestId('manage-vega-wallet').click();
|
||||
cy.getByTestId('disconnect').click();
|
||||
cy.getByTestId(banner).should('not.exist');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
const viewToggle = '[data-testid="epoch-reward-view-toggle-total"]';
|
||||
const warning = '[data-testid="callout"]';
|
||||
|
||||
context(
|
||||
'Rewards Page - verify elements on page',
|
||||
{ tags: '@regression' },
|
||||
function () {
|
||||
before('navigate to rewards page', function () {
|
||||
cy.visit('/').navigate_to('rewards');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
it('should have REWARDS tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('rewards');
|
||||
});
|
||||
|
||||
it('should have rewards header visible', function () {
|
||||
cy.verify_page_header('Rewards and fees');
|
||||
});
|
||||
|
||||
it('should have epoch warning', function () {
|
||||
cy.get(warning)
|
||||
.should('be.visible')
|
||||
.and(
|
||||
'have.text',
|
||||
'Rewards are credited 5 minutes after the epoch ends.This delay is set by a network parameter'
|
||||
);
|
||||
});
|
||||
|
||||
it('should have toggle for seeing total vs individual rewards', function () {
|
||||
cy.get(viewToggle).should('be.visible');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -17,7 +17,8 @@ const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
|
||||
|
||||
context('Verify elements on Token page', { tags: '@smoke' }, function () {
|
||||
before('Visit token page', function () {
|
||||
cy.visit('/token');
|
||||
cy.visit('/');
|
||||
cy.navigate_to('token');
|
||||
});
|
||||
describe('THE $VEGA TOKEN table', function () {
|
||||
it('should have TOKEN ADDRESS', function () {
|
||||
|
||||
@@ -8,7 +8,13 @@ context(
|
||||
function () {
|
||||
before('visit homepage', function () {
|
||||
cy.intercept('GET', '**/tranches/stats', { tranches });
|
||||
cy.visit('/token/tranches');
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('Able to navigate to tranches page', function () {
|
||||
cy.navigate_to('supply');
|
||||
cy.url().should('include', '/token/tranches');
|
||||
cy.get('h1').should('contain.text', 'Vesting tranches');
|
||||
});
|
||||
|
||||
// 1005-VEST-001
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/// <reference types="cypress" />
|
||||
const guideLink = '[data-testid="staking-guide-link"]';
|
||||
const validatorTitle = '[data-testid="validator-node-title"]';
|
||||
const validatorId = '[data-testid="validator-id"]';
|
||||
const validatorPubKey = '[data-testid="validator-public-key"]';
|
||||
@@ -24,7 +25,31 @@ const stakeNumberRegex = /^\d*\.?\d*$/;
|
||||
|
||||
context('Staking Page - verify elements on page', function () {
|
||||
before('navigate to staking page', function () {
|
||||
cy.visit('/validators');
|
||||
cy.visit('/').navigate_to('validators');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', { tags: '@smoke' }, function () {
|
||||
describe('description section', function () {
|
||||
it('Should have validators tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('validators');
|
||||
});
|
||||
|
||||
it('Should have validators ON VEGA header visible', function () {
|
||||
cy.verify_page_header('Validators');
|
||||
});
|
||||
|
||||
it('Should have Staking Guide link visible', function () {
|
||||
// 2001-STKE-003
|
||||
cy.get(guideLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Read more about staking on Vega')
|
||||
.and(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://docs.vega.xyz/mainnet/concepts/vega-chain/#staking-on-vega'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const connectButton = '[data-testid="connect-to-eth-btn"]';
|
||||
const lockedTokensInVestingContract = '6,499,972.30';
|
||||
|
||||
context(
|
||||
@@ -5,7 +6,24 @@ context(
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
before('navigate to vesting page', function () {
|
||||
cy.visit('/token/redeem');
|
||||
cy.visit('/').navigate_to('vesting');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
it('should have vesting tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('token');
|
||||
});
|
||||
|
||||
it('should have VESTING header visible', function () {
|
||||
cy.verify_page_header('Vesting');
|
||||
});
|
||||
|
||||
// 1005-VEST-018
|
||||
it('should have connect Eth wallet button', function () {
|
||||
cy.get(connectButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Ethereum wallet');
|
||||
});
|
||||
});
|
||||
|
||||
describe('With Eth wallet connected', function () {
|
||||
@@ -20,18 +38,15 @@ context(
|
||||
cy.getByTestId('currency-title')
|
||||
.should('contain.text', 'VEGA')
|
||||
.and('contain.text', 'In vesting contract');
|
||||
cy.get('[data-testid="currency-value"]:visible').should(
|
||||
cy.getByTestId('currency-value').should(
|
||||
'have.text',
|
||||
lockedTokensInVestingContract
|
||||
);
|
||||
cy.get('[data-testid="currency-locked"]:visible').should(
|
||||
cy.getByTestId('currency-locked').should(
|
||||
'have.text',
|
||||
lockedTokensInVestingContract
|
||||
);
|
||||
cy.get('[data-testid="currency-unlocked"]:visible').should(
|
||||
'have.text',
|
||||
'0.00'
|
||||
);
|
||||
cy.getByTestId('currency-unlocked').should('have.text', '0.00');
|
||||
});
|
||||
});
|
||||
// 1005-VEST-022 1005-VEST-023
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
const walletContainer = 'aside [data-testid="ethereum-wallet"]';
|
||||
const walletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const walletHeader = '[data-testid="wallet-header"] h1';
|
||||
const connectToEthButton =
|
||||
'[data-testid="connect-to-eth-wallet-button"]:visible';
|
||||
const connectToEthButton = '[data-testid="connect-to-eth-wallet-button"]';
|
||||
const connectorList = '[data-testid="web3-connector-list"]';
|
||||
const associate = '[href="/token/associate"]';
|
||||
const disassociate = '[href="/token/disassociate"]';
|
||||
const disconnect = '[data-testid="disconnect-from-eth-wallet-button"]';
|
||||
const accountNo = '[data-testid="ethereum-account-truncated"]';
|
||||
const currencyTitle = '[data-testid="currency-title"]:visible';
|
||||
const currencyValue = '[data-testid="currency-value"]:visible';
|
||||
const vegaInVesting = '[data-testid="vega-in-vesting-contract"]:visible';
|
||||
const vegaInWallet = '[data-testid="vega-in-wallet"]:visible';
|
||||
const progressBar = '[data-testid="progress-bar"]:visible';
|
||||
const currencyLocked = '[data-testid="currency-locked"]:visible';
|
||||
const currencyUnlocked = '[data-testid="currency-unlocked"]:visible';
|
||||
const currencyTitle = '[data-testid="currency-title"]';
|
||||
const currencyValue = '[data-testid="currency-value"]';
|
||||
const vegaInVesting = '[data-testid="vega-in-vesting-contract"]';
|
||||
const vegaInWallet = '[data-testid="vega-in-wallet"]';
|
||||
const progressBar = '[data-testid="progress-bar"]';
|
||||
const currencyLocked = '[data-testid="currency-locked"]';
|
||||
const currencyUnlocked = '[data-testid="currency-unlocked"]';
|
||||
const dialog = '[role="dialog"]';
|
||||
const dialogHeader = '[data-testid="dialog-title"]';
|
||||
const dialogCloseBtn = '[data-testid="dialog-close"]';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
|
||||
const walletContainer = 'aside [data-testid="vega-wallet"]';
|
||||
const walletContainer = '[data-testid="vega-wallet"]';
|
||||
const walletHeader = '[data-testid="wallet-header"] h1';
|
||||
const connectButton = '[data-testid="connect-vega-wallet"]';
|
||||
const getVegaLink = '[data-testid="link"]';
|
||||
@@ -14,6 +14,7 @@ const restWallet = '#wallet';
|
||||
const restPassphrase = '#passphrase';
|
||||
const restConnectBtn = '[type="submit"]';
|
||||
const accountNo = '[data-testid="vega-account-truncated"]';
|
||||
const walletName = '[data-testid="wallet-name"]';
|
||||
const currencyTitle = '[data-testid="currency-title"]';
|
||||
const currencyValue = '[data-testid="currency-value"]';
|
||||
const vegaUnstaked = '[data-testid="vega-wallet-balance-unstaked"] .text-right';
|
||||
@@ -27,24 +28,44 @@ const vegaWalletCurrencyTitle = '[data-testid="currency-title"]';
|
||||
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
|
||||
const faucetAssets = {
|
||||
BTCFake: 'fBTC',
|
||||
DAIFake: 'fDAI',
|
||||
EUROFake: 'fEURO',
|
||||
USDCFake: 'fUSDC',
|
||||
};
|
||||
|
||||
context(
|
||||
'Vega Wallet - verify elements on widget',
|
||||
{ tags: '@regression' },
|
||||
() => {
|
||||
before('visit token home page', () => {
|
||||
function () {
|
||||
before('visit token home page', function () {
|
||||
cy.visit('/');
|
||||
cy.get(walletContainer, { timeout: 60000 }).should('be.visible');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', () => {
|
||||
it('should have required elements visible', function () {
|
||||
describe('with wallets disconnected', function () {
|
||||
before('wait for widget to load', function () {
|
||||
cy.get(walletContainer, { timeout: 10000 }).should('be.visible');
|
||||
});
|
||||
|
||||
it('should have VEGA WALLET header visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(walletHeader)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Vega Wallet');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Connect Vega button visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(connectButton)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet to use associated $VEGA');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Get a Vega wallet link visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(getVegaLink)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Get a Vega wallet')
|
||||
@@ -53,14 +74,14 @@ context(
|
||||
});
|
||||
});
|
||||
|
||||
describe('when connect button clicked', () => {
|
||||
before('click connect vega wallet button', () => {
|
||||
describe('when connect button clicked', function () {
|
||||
before('click connect vega wallet button', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(connectButton).click();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Connect Vega header visible', () => {
|
||||
it('should have Connect Vega header visible', function () {
|
||||
cy.get(dialog).within(() => {
|
||||
cy.get(walletDialogHeader)
|
||||
.should('be.visible')
|
||||
@@ -154,6 +175,14 @@ context(
|
||||
}
|
||||
);
|
||||
|
||||
it.skip('should have wallet name visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(walletName)
|
||||
.should('be.visible')
|
||||
.and('have.text', `${Cypress.env('vegaWalletName')} key 1`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have Vega Associated currency title visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(currencyTitle)
|
||||
@@ -270,71 +299,113 @@ context(
|
||||
});
|
||||
|
||||
// 2002-SINC-016
|
||||
describe('Vega wallet with assets', function () {
|
||||
const assets = [
|
||||
{
|
||||
id: 'fUSDC',
|
||||
name: 'USDC (fake)',
|
||||
amount: '1000000',
|
||||
expectedAmount: '10.00',
|
||||
},
|
||||
{
|
||||
id: 'fDAI',
|
||||
name: 'DAI (fake)',
|
||||
amount: '200000',
|
||||
expectedAmount: '2.00',
|
||||
},
|
||||
{
|
||||
id: 'fBTC',
|
||||
name: 'BTC (fake)',
|
||||
amount: '600000',
|
||||
expectedAmount: '6.00',
|
||||
},
|
||||
{
|
||||
id: 'fEURO',
|
||||
name: 'EURO (fake)',
|
||||
amount: '800000',
|
||||
expectedAmount: '8.00',
|
||||
},
|
||||
];
|
||||
|
||||
before('faucet assets to connected vega wallet', function () {
|
||||
for (const { id, amount } of assets) {
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
id,
|
||||
amount,
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
}
|
||||
describe('when assets exist in vegawallet', function () {
|
||||
before('send-faucet assets to connected vega wallet', function () {
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.USDCFake,
|
||||
'1000000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.BTCFake,
|
||||
'600000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.EUROFake,
|
||||
'800000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.vega_wallet_faucet_assets_without_check(
|
||||
faucetAssets.DAIFake,
|
||||
'200000',
|
||||
vegaWalletPublicKey
|
||||
);
|
||||
cy.reload();
|
||||
cy.wait_for_spinner();
|
||||
cy.connectVegaWallet();
|
||||
cy.ethereum_wallet_connect();
|
||||
});
|
||||
|
||||
for (const { id, name, expectedAmount } of assets) {
|
||||
it(`should see ${id} within vega wallet`, () => {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(id, txTimeout)
|
||||
.should('be.visible');
|
||||
it('should see fUSDC assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.USDCFake, name: 'USDC (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within((el) => {
|
||||
const value = parseFloat(el.text());
|
||||
cy.wrap(value).should('be.gte', parseFloat(expectedAmount));
|
||||
});
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(id)
|
||||
.parent()
|
||||
.contains(name);
|
||||
});
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.invoke('text')
|
||||
.should('not.be.empty');
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should see fBTC assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.BTCFake, name: 'BTC (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within(() => cy.contains_exactly('6.00').should('be.visible'));
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
});
|
||||
});
|
||||
|
||||
it('should see fEURO assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.EUROFake, name: 'EURO (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within(() => cy.contains_exactly('8.00').should('be.visible'));
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
});
|
||||
});
|
||||
|
||||
it('should see fDAI assets - within vega wallet', function () {
|
||||
let currency = { id: faucetAssets.DAIFake, name: 'DAI (fake)' };
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id, txTimeout)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.siblings()
|
||||
.within(() => cy.contains_exactly('2.00').should('be.visible'));
|
||||
|
||||
cy.get(vegaWalletCurrencyTitle)
|
||||
.contains(currency.id)
|
||||
.parent()
|
||||
.contains(currency.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
const connectToVegaBtn = '[data-testid="connect-to-vega-wallet-btn"]';
|
||||
|
||||
context(
|
||||
'Withdraw Page - verify elements on page',
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
before('navigate to withdrawals page', function () {
|
||||
cy.visit('/').navigate_to('withdraw');
|
||||
});
|
||||
|
||||
describe('with wallets disconnected', function () {
|
||||
it('should have withdraw tab highlighted', function () {
|
||||
cy.verify_tab_highlighted('token');
|
||||
});
|
||||
|
||||
it('should have WITHDRAW header visible', function () {
|
||||
cy.verify_page_header('Withdrawals');
|
||||
});
|
||||
|
||||
it('should have connect Vega wallet button', function () {
|
||||
cy.get(connectToVegaBtn)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Connect Vega wallet');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -22,32 +22,25 @@ const navigation = {
|
||||
};
|
||||
|
||||
const topLevelRoutes = ['proposals', 'validators', 'rewards'];
|
||||
const tokenDropDown = 'state-trigger';
|
||||
|
||||
Cypress.Commands.add('navigate_to', (page) => {
|
||||
const tokenDropDown = 'state-trigger';
|
||||
|
||||
if (!topLevelRoutes.includes(page)) {
|
||||
// FIXME: Timeout madness
|
||||
cy.getByTestId(tokenDropDown, { timeout: 60000 }).eq(0).click();
|
||||
cy.get('[data-testid="token-dropdown"]:visible').within(() => {
|
||||
cy.get(navigation[page]).eq(0).click();
|
||||
cy.getByTestId(tokenDropDown, { timeout: 10000 }).click();
|
||||
cy.getByTestId('token-dropdown').within(() => {
|
||||
cy.get(navigation[page]).click();
|
||||
});
|
||||
} else {
|
||||
return cy.get(navigation.section, { timeout: 10000 }).within(() => {
|
||||
cy.get(navigation[page]).eq(0).click();
|
||||
cy.get(navigation[page]).click();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Cypress.Commands.add('verify_tab_highlighted', (page) => {
|
||||
return cy.get(navigation.section).within(() => {
|
||||
if (!topLevelRoutes.includes(page)) {
|
||||
cy.getByTestId(tokenDropDown, { timeout: 10000 }).eq(0).click();
|
||||
cy.get('[data-testid="token-dropdown"]:visible').within(() => {
|
||||
cy.get(navigation[page]).should('have.attr', 'aria-current');
|
||||
});
|
||||
} else {
|
||||
cy.get(navigation[page]).should('have.attr', 'aria-current');
|
||||
}
|
||||
cy.get(navigation[page]).should('have.attr', 'aria-current');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,7 +59,7 @@ export function associateTokenStartOfTests() {
|
||||
cy.highlight(`Associating tokens for first time`);
|
||||
cy.ethereum_wallet_connect();
|
||||
cy.connectVegaWallet();
|
||||
cy.get('[href="/token/associate"]:visible').first().click();
|
||||
cy.get('[href="/token/associate"]').first().click();
|
||||
cy.getByTestId('associate-radio-wallet', { timeout: 30000 }).click();
|
||||
cy.getByTestId('token-amount-input', epochTimeout).type('1');
|
||||
cy.getByTestId('token-input-submit-button', txTimeout)
|
||||
|
||||
@@ -82,10 +82,9 @@ Cypress.Commands.add(
|
||||
Cypress.Commands.add(
|
||||
'get_submitted_proposal_from_proposal_list',
|
||||
(proposalTitle) => {
|
||||
cy.get_proposal_id_from_list(proposalTitle).then(() => {
|
||||
cy.get('@proposalIdText').then((proposalId) => {
|
||||
return cy.get(`#${proposalId}`);
|
||||
});
|
||||
cy.get_proposal_id_from_list(proposalTitle);
|
||||
cy.get('@proposalIdText').then((proposalId) => {
|
||||
return cy.get(`#${proposalId}`);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,10 +3,10 @@ const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
|
||||
const tokenInputApprove = '[data-testid="token-input-approve-button"]';
|
||||
const addStakeRadioButton = '[data-testid="add-stake-radio"]';
|
||||
const removeStakeRadioButton = '[data-testid="remove-stake-radio"]';
|
||||
const ethWalletAssociateButton = '[href="/token/associate"]:visible';
|
||||
const ethWalletDissociateButton = '[href="/token/disassociate"]:visible';
|
||||
const ethWalletAssociateButton = '[href="/token/associate"]';
|
||||
const ethWalletDissociateButton = '[href="/token/disassociate"]';
|
||||
const vegaWalletUnstakedBalance =
|
||||
'[data-testid="vega-wallet-balance-unstaked"]:visible';
|
||||
'[data-testid="vega-wallet-balance-unstaked"]';
|
||||
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
|
||||
const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]';
|
||||
const associateContractRadioButton = '[data-testid="associate-radio-contract"]';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]:visible';
|
||||
const connectToEthButton =
|
||||
'[data-testid="connect-to-eth-wallet-button"]:visible';
|
||||
const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const connectToEthButton = '[data-testid="connect-to-eth-wallet-button"]';
|
||||
const capsuleWalletConnectButton = '[data-testid="web3-connector-Unknown"]';
|
||||
|
||||
Cypress.Commands.add('ethereum_wallet_connect', () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@vegaprotocol/smart-contracts';
|
||||
import { ethers, Wallet } from 'ethers';
|
||||
|
||||
const vegaWalletContainer = 'aside [data-testid="vega-wallet"]';
|
||||
const vegaWalletContainer = '[data-testid="vega-wallet"]';
|
||||
const vegaWalletMnemonic = Cypress.env('vegaWalletMnemonic');
|
||||
const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey');
|
||||
const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
|
||||
@@ -95,7 +95,7 @@ Cypress.Commands.add('faucet_asset', function (assetEthAddress) {
|
||||
});
|
||||
|
||||
Cypress.Commands.add('vega_wallet_teardown', function () {
|
||||
cy.get('aside [data-testid="associated-amount"]')
|
||||
cy.get('[data-testid="associated-amount"]')
|
||||
.should('be.visible')
|
||||
.invoke('text')
|
||||
.as('associatedAmount');
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import classNames from 'classnames';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { EthWallet } from '../eth-wallet';
|
||||
import { VegaWallet } from '../vega-wallet';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Route {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
const DrawerSection = ({ children }: { children: React.ReactNode }) => (
|
||||
<div className="px-4 my-4">{children}</div>
|
||||
);
|
||||
|
||||
const IconLine = ({ inverted }: { inverted: boolean }) => (
|
||||
<span className={`block w-6 h-[2px] ${inverted ? 'bg-black' : 'bg-white'}`} />
|
||||
);
|
||||
|
||||
const DrawerNavLinks = ({
|
||||
isInverted,
|
||||
routes,
|
||||
}: {
|
||||
isInverted?: boolean;
|
||||
routes: Route[];
|
||||
}) => {
|
||||
const { appDispatch } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
const linkProps = {
|
||||
end: true,
|
||||
onClick: () =>
|
||||
appDispatch({ type: AppStateActionType.SET_DRAWER, isOpen: false }),
|
||||
};
|
||||
const navClasses = classNames('flex flex-col');
|
||||
|
||||
return (
|
||||
<nav className={navClasses}>
|
||||
{routes.map(({ name, path }) => {
|
||||
return (
|
||||
<NavLink
|
||||
{...linkProps}
|
||||
to={{ pathname: path }}
|
||||
className={({ isActive }) =>
|
||||
classNames({
|
||||
'bg-vega-yellow text-black': !isInverted && isActive,
|
||||
'bg-transparent text-white hover:text-vega-yellow':
|
||||
!isInverted && !isActive,
|
||||
'bg-black text-white': isInverted && isActive,
|
||||
'bg-transparent text-black hover:text-white':
|
||||
isInverted && !isActive,
|
||||
'border-t border-white p-4': true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t(name)}
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export const NavDrawer = ({
|
||||
inverted,
|
||||
routes,
|
||||
}: {
|
||||
inverted: boolean;
|
||||
routes: Route[];
|
||||
}) => {
|
||||
const { appState, appDispatch } = useAppState();
|
||||
|
||||
const drawerContentClasses = classNames(
|
||||
'drawer-content', // needed for css animation
|
||||
// Positions the modal in the center of screen
|
||||
'fixed w-[80vw] max-w-[420px] top-0 right-0',
|
||||
'flex flex-col flex-nowrap justify-between h-full bg-banner overflow-y-scroll border-l border-white',
|
||||
'bg-black text-neutral-200'
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_DRAWER,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
className="flex flex-col flex-nowrap gap-1"
|
||||
>
|
||||
<IconLine inverted={inverted} />
|
||||
<IconLine inverted={inverted} />
|
||||
<IconLine inverted={inverted} />
|
||||
</button>
|
||||
|
||||
<Dialog.Root
|
||||
open={appState.drawerOpen}
|
||||
onOpenChange={(isOpen) =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_DRAWER,
|
||||
isOpen,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-white/15" />
|
||||
<Dialog.Content className={drawerContentClasses}>
|
||||
<div>
|
||||
<DrawerSection>
|
||||
<EthWallet />
|
||||
</DrawerSection>
|
||||
<DrawerSection>
|
||||
<VegaWallet />
|
||||
</DrawerSection>
|
||||
</div>
|
||||
<DrawerNavLinks routes={routes} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useState } from 'react';
|
||||
import Routes, { TOKEN_DROPDOWN_ROUTES } from '../../routes/routes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { NavbarTheme } from './nav-link';
|
||||
import { AppNavLink } from './nav-link';
|
||||
import {
|
||||
NavDropdownMenu,
|
||||
NavDropdownMenuContent,
|
||||
NavDropdownMenuItem,
|
||||
NavDropdownMenuTrigger,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const NavDropDown = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
|
||||
const { t } = useTranslation();
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
return (
|
||||
<NavDropdownMenu open={isOpen} onOpenChange={(open) => setOpen(open)}>
|
||||
<AppNavLink
|
||||
name={
|
||||
<NavDropdownMenuTrigger
|
||||
className="w-auto flex items-center -m-3 p-3 cursor-pointer"
|
||||
data-testid="state-trigger"
|
||||
onClick={() => setOpen(!isOpen)}
|
||||
>
|
||||
{t('Token')}
|
||||
</NavDropdownMenuTrigger>
|
||||
}
|
||||
testId="token-dd"
|
||||
path={Routes.TOKEN}
|
||||
navbarTheme={navbarTheme}
|
||||
/>
|
||||
|
||||
<NavDropdownMenuContent data-testid="token-dropdown">
|
||||
{TOKEN_DROPDOWN_ROUTES.map((r) => (
|
||||
<NavDropdownMenuItem key={r.name} onClick={() => setOpen(false)}>
|
||||
<AppNavLink
|
||||
testId={r.name}
|
||||
name={t(r.name)}
|
||||
path={r.path}
|
||||
navbarTheme={'inherit'}
|
||||
subNav={true}
|
||||
end={true}
|
||||
fullWidth={true}
|
||||
/>
|
||||
</NavDropdownMenuItem>
|
||||
))}
|
||||
</NavDropdownMenuContent>
|
||||
</NavDropdownMenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import classNames from 'classnames';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import type { HTMLAttributeAnchorTarget, ReactNode } from 'react';
|
||||
import { getNavLinkClassNames } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export type NavbarTheme = 'inherit' | 'dark' | 'yellow';
|
||||
|
||||
interface AppNavLinkProps {
|
||||
name: ReactNode | string;
|
||||
path: string;
|
||||
navbarTheme: NavbarTheme;
|
||||
testId?: string;
|
||||
target?: HTMLAttributeAnchorTarget;
|
||||
end?: boolean;
|
||||
fullWidth?: boolean;
|
||||
subNav?: boolean;
|
||||
}
|
||||
|
||||
export const AppNavLink = ({
|
||||
name,
|
||||
path,
|
||||
navbarTheme,
|
||||
target,
|
||||
testId,
|
||||
end = false,
|
||||
fullWidth = false,
|
||||
subNav = false,
|
||||
}: AppNavLinkProps) => {
|
||||
const borderClasses = classNames(
|
||||
'absolute h-0.5 w-full bottom-[-1px] left-0',
|
||||
{
|
||||
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
|
||||
'bg-black': navbarTheme === 'yellow',
|
||||
}
|
||||
);
|
||||
return (
|
||||
<NavLink
|
||||
key={path}
|
||||
data-testid={testId}
|
||||
to={{ pathname: path }}
|
||||
className={getNavLinkClassNames(navbarTheme, fullWidth, subNav)}
|
||||
target={target}
|
||||
end={end}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
return (
|
||||
<div className={subNav ? 'inline-block relative pb-1' : undefined}>
|
||||
{name}
|
||||
{isActive && (
|
||||
<span data-testid="link-active" className={borderClasses} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-content[data-state='open'] {
|
||||
animation: slideIn 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
.drawer-content[data-state='closed'] {
|
||||
animation: slideOut 150ms ease-in forwards;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Nav } from './nav';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
NetworkSwitcher: () => <div data-testid="network-switcher" />,
|
||||
useEnvironment: () => ({ VEGA_ENV: 'MAINNET' }),
|
||||
}));
|
||||
|
||||
const renderComponent = (initialEntries?: string[]) => {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={initialEntries}>
|
||||
<Nav />
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
describe('nav', () => {
|
||||
it('Renders logo with link to home', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('logo-link')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/'
|
||||
);
|
||||
});
|
||||
it('Renders network switcher', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('network-switcher')).toBeInTheDocument();
|
||||
});
|
||||
it('Renders all top level routes', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('Proposals')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/proposals'
|
||||
);
|
||||
expect(screen.getByTestId('Validators')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/validators'
|
||||
);
|
||||
expect(screen.getByTestId('Rewards')).toHaveProperty(
|
||||
'href',
|
||||
'http://localhost/rewards'
|
||||
);
|
||||
});
|
||||
it('Shows active state on dropdown trigger when on home route for subroutes', () => {
|
||||
const { getByTestId } = renderComponent(['/token']);
|
||||
const dd = getByTestId('token-dd');
|
||||
expect(within(dd).getByTestId('link-active')).toBeInTheDocument();
|
||||
});
|
||||
it('Shows active state on dropdown trigger when on sub route of dropdown', () => {
|
||||
const { getByTestId } = renderComponent(['/token/withdraw']);
|
||||
const dd = getByTestId('token-dd');
|
||||
expect(within(dd).getByTestId('link-active')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,85 +1,81 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { NetworkSwitcher } from '@vegaprotocol/environment';
|
||||
import { TOKEN_DROPDOWN_ROUTES, TOP_LEVEL_ROUTES } from '../../routes/routes';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { TOP_LEVEL_ROUTES } from '../../routes/routes';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { NavigationProps } from '@vegaprotocol/ui-toolkit';
|
||||
import { useNavigationDrawer } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Navigation,
|
||||
NavigationBreakpoint,
|
||||
NavigationContent,
|
||||
NavigationItem,
|
||||
NavigationLink,
|
||||
NavigationList,
|
||||
NavigationTrigger,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { EthWallet } from '../eth-wallet';
|
||||
import { VegaWallet } from '../vega-wallet';
|
||||
import { useLocation, useMatch } from 'react-router-dom';
|
||||
import { useEffect } from 'react';
|
||||
import logoWhiteText from '../../images/logo-white-text.png';
|
||||
import logoBlackText from '../../images/logo-black-text.png';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { NavDrawer } from './nav-draw';
|
||||
import { Nav as ToolkitNav } from '@vegaprotocol/ui-toolkit';
|
||||
import { AppNavLink } from './nav-link';
|
||||
import { NavDropDown } from './nav-dropdown';
|
||||
|
||||
export const Nav = ({ theme }: Pick<NavigationProps, 'theme'>) => {
|
||||
const { t } = useTranslation();
|
||||
const setDrawerOpen = useNavigationDrawer((state) => state.setDrawerOpen);
|
||||
const useDebouncedResize = () => {
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
|
||||
const location = useLocation();
|
||||
const isOnToken = useMatch('token/*');
|
||||
useEffect(() => {
|
||||
setDrawerOpen(false);
|
||||
}, [location, setDrawerOpen]);
|
||||
const handleResizeDebounced = debounce(() => {
|
||||
setWindowWidth(window.innerWidth);
|
||||
}, 300);
|
||||
|
||||
const topLevel = TOP_LEVEL_ROUTES.map(({ name, path }) => (
|
||||
<NavigationItem key={name}>
|
||||
<NavigationLink to={path}>{name}</NavigationLink>
|
||||
</NavigationItem>
|
||||
));
|
||||
window.addEventListener('resize', handleResizeDebounced);
|
||||
|
||||
const secondLevel = TOKEN_DROPDOWN_ROUTES.map(({ name, path, end }) => (
|
||||
<NavigationItem key={name}>
|
||||
<NavigationLink to={path} end={Boolean(end)}>
|
||||
{name}
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
));
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResizeDebounced);
|
||||
};
|
||||
}, []);
|
||||
return {
|
||||
windowWidth,
|
||||
};
|
||||
};
|
||||
|
||||
type NavbarTheme = 'inherit' | 'dark' | 'yellow';
|
||||
interface NavbarProps {
|
||||
navbarTheme?: NavbarTheme;
|
||||
}
|
||||
|
||||
export const Nav = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
||||
const { windowWidth } = useDebouncedResize();
|
||||
const isDesktop = windowWidth > 995;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const isYellow = navbarTheme === 'yellow';
|
||||
|
||||
return (
|
||||
<Navigation appName="Governance" theme={theme} breakpoints={[458, 959]}>
|
||||
<NavigationList
|
||||
className="[.drawer-content_&]:border-b [.drawer-content_&]:border-b-vega-light-200 dark:[.drawer-content_&]:border-b-vega-dark-200 [.drawer-content_&]:pb-8 [.drawer-content_&]:mb-2"
|
||||
hide={[NavigationBreakpoint.Small]}
|
||||
>
|
||||
<NavigationItem className="[.drawer-content_&]:w-full">
|
||||
<NetworkSwitcher className="[.drawer-content_&]:w-full" />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Narrow, NavigationBreakpoint.Small]}
|
||||
>
|
||||
{topLevel}
|
||||
<NavigationItem>
|
||||
<NavigationTrigger
|
||||
data-testid="state-trigger"
|
||||
isActive={Boolean(isOnToken)}
|
||||
>
|
||||
{t('Token')}
|
||||
</NavigationTrigger>
|
||||
<NavigationContent>
|
||||
<NavigationList data-testid="token-dropdown">
|
||||
{secondLevel}
|
||||
</NavigationList>
|
||||
</NavigationContent>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={true}
|
||||
className="[.drawer-content_&]:border-t [.drawer-content_&]:border-t-vega-light-200 dark:[.drawer-content_&]:border-t-vega-dark-200 [.drawer-content_&]:pt-8 [.drawer-content_&]:mt-4"
|
||||
>
|
||||
<NavigationItem className="[.drawer-content_&]:w-full">
|
||||
<EthWallet />
|
||||
</NavigationItem>
|
||||
<NavigationItem className="[.drawer-content_&]:w-full">
|
||||
<VegaWallet />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
</Navigation>
|
||||
<ToolkitNav
|
||||
navbarTheme={navbarTheme}
|
||||
icon={
|
||||
<Link to="/" data-testid="logo-link">
|
||||
<img
|
||||
alt="Vega"
|
||||
src={navbarTheme === 'yellow' ? logoBlackText : logoWhiteText}
|
||||
height={30}
|
||||
width={250}
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
title={undefined}
|
||||
titleContent={<NetworkSwitcher />}
|
||||
>
|
||||
{isDesktop ? (
|
||||
<nav className="flex items-center flex-1 px-4">
|
||||
{TOP_LEVEL_ROUTES.map((r) => (
|
||||
<AppNavLink
|
||||
key={r.path}
|
||||
testId={r.name}
|
||||
name={t(r.name)}
|
||||
path={r.path}
|
||||
navbarTheme={navbarTheme}
|
||||
/>
|
||||
))}
|
||||
<NavDropDown navbarTheme={navbarTheme} />
|
||||
</nav>
|
||||
) : (
|
||||
<nav className="flex items-center flex-1 px-2 justify-end">
|
||||
<NavDrawer inverted={isYellow} routes={TOP_LEVEL_ROUTES} />
|
||||
</nav>
|
||||
)}
|
||||
</ToolkitNav>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,13 +21,13 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
|
||||
<>
|
||||
<AnnouncementBanner>
|
||||
<div className="font-alpha calt uppercase text-center text-lg text-white">
|
||||
<span className="pr-4">Mainnet sim 3 is coming soon!</span>
|
||||
<span className="pr-4">Mainnet sim 2 is live!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">
|
||||
Come help stress test the network
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</AnnouncementBanner>
|
||||
<Nav theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
|
||||
<Nav navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
|
||||
{isReadOnly ? (
|
||||
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
|
||||
) : null}
|
||||
|
||||
@@ -37,6 +37,9 @@ export interface AppState {
|
||||
/** Whether or not the connect to Ethereum wallet overlay is open */
|
||||
ethConnectOverlay: boolean;
|
||||
|
||||
/** Whether or not the mobile drawer is open. Only relevant on screens smaller than 960 */
|
||||
drawerOpen: boolean;
|
||||
|
||||
/** Whether or not the transaction modal is open */
|
||||
transactionOverlay: boolean;
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ const initialAppState: AppState = {
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
drawerOpen: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
};
|
||||
@@ -35,6 +36,7 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
|
||||
return {
|
||||
...state,
|
||||
vegaWalletOverlay: action.isOpen,
|
||||
drawerOpen: action.isOpen ? false : state.drawerOpen,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_VEGA_WALLET_MANAGE_OVERLAY: {
|
||||
@@ -42,17 +44,20 @@ function appStateReducer(state: AppState, action: AppStateAction): AppState {
|
||||
...state,
|
||||
vegaWalletManageOverlay: action.isOpen,
|
||||
vegaWalletOverlay: action.isOpen ? false : state.vegaWalletOverlay,
|
||||
drawerOpen: action.isOpen ? false : state.drawerOpen,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_ETH_WALLET_OVERLAY: {
|
||||
return {
|
||||
...state,
|
||||
ethConnectOverlay: action.isOpen,
|
||||
drawerOpen: action.isOpen ? false : state.drawerOpen,
|
||||
};
|
||||
}
|
||||
case AppStateActionType.SET_DRAWER: {
|
||||
return {
|
||||
...state,
|
||||
drawerOpen: action.isOpen,
|
||||
vegaWalletOverlay: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,28 +9,26 @@ export type PendingTxsStore = {
|
||||
resetPendingTxs: () => void;
|
||||
};
|
||||
|
||||
export const usePendingBalancesStore = create<PendingTxsStore>()(
|
||||
(set, get) => ({
|
||||
pendingBalances: [],
|
||||
addPendingTxs: (event: Event[]) => {
|
||||
set({
|
||||
pendingBalances: uniqBy(
|
||||
[...get().pendingBalances, ...event],
|
||||
'transactionHash'
|
||||
export const usePendingBalancesStore = create<PendingTxsStore>((set, get) => ({
|
||||
pendingBalances: [],
|
||||
addPendingTxs: (event: Event[]) => {
|
||||
set({
|
||||
pendingBalances: uniqBy(
|
||||
[...get().pendingBalances, ...event],
|
||||
'transactionHash'
|
||||
),
|
||||
});
|
||||
},
|
||||
removePendingTx: (event: Event) => {
|
||||
set({
|
||||
pendingBalances: [
|
||||
...get().pendingBalances.filter(
|
||||
({ transactionHash }) => transactionHash !== event.transactionHash
|
||||
),
|
||||
});
|
||||
},
|
||||
removePendingTx: (event: Event) => {
|
||||
set({
|
||||
pendingBalances: [
|
||||
...get().pendingBalances.filter(
|
||||
({ transactionHash }) => transactionHash !== event.transactionHash
|
||||
),
|
||||
],
|
||||
});
|
||||
},
|
||||
resetPendingTxs: () => {
|
||||
set({ pendingBalances: [] });
|
||||
},
|
||||
})
|
||||
);
|
||||
],
|
||||
});
|
||||
},
|
||||
resetPendingTxs: () => {
|
||||
set({ pendingBalances: [] });
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface RefreshBalances {
|
||||
vestingAssociatedBalance: BigNumber;
|
||||
}
|
||||
|
||||
export const useBalances = create<BalancesStore>()((set) => ({
|
||||
export const useBalances = create<BalancesStore>((set) => ({
|
||||
associationBreakdown: {
|
||||
stakingAssociations: {},
|
||||
vestingAssociations: {},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { toBigNum } from '@vegaprotocol/utils';
|
||||
import type { TrancheServiceResponse } from '@vegaprotocol/smart-contracts';
|
||||
import type BigNumber from 'bignumber.js';
|
||||
import { create } from 'zustand';
|
||||
import create from 'zustand';
|
||||
import { ENV } from '../../config';
|
||||
|
||||
export interface Tranche {
|
||||
@@ -36,7 +36,7 @@ export type TranchesStore = {
|
||||
|
||||
const secondsToDate = (seconds: number) => new Date(seconds * 1000);
|
||||
|
||||
export const useTranches = create<TranchesStore>()((set) => ({
|
||||
export const useTranches = create<TranchesStore>((set) => ({
|
||||
tranches: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
@@ -54,6 +54,7 @@ const mockAppState: AppState = {
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
drawerOpen: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ export const VoteDetails = ({
|
||||
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
|
||||
<section>
|
||||
<SubHeading title={t('liquidityVotes')} />
|
||||
<p data-testid="liquidity-votes-status">
|
||||
<p>
|
||||
<span>
|
||||
<CurrentProposalStatus proposal={proposal} />
|
||||
</span>
|
||||
@@ -105,7 +105,7 @@ export const VoteDetails = ({
|
||||
)}
|
||||
<section data-testid="votes-table">
|
||||
<SubHeading title={t('tokenVotes')} />
|
||||
<p data-testid="token-votes-status">
|
||||
<p>
|
||||
<span>
|
||||
<CurrentProposalStatus proposal={proposal} />
|
||||
</span>
|
||||
|
||||
@@ -17,6 +17,7 @@ const mockAppState: AppState = {
|
||||
vegaWalletOverlay: false,
|
||||
vegaWalletManageOverlay: false,
|
||||
ethConnectOverlay: false,
|
||||
drawerOpen: false,
|
||||
transactionOverlay: false,
|
||||
bannerMessage: '',
|
||||
};
|
||||
|
||||
@@ -37,7 +37,6 @@ export const TOKEN_DROPDOWN_ROUTES = [
|
||||
{
|
||||
name: 'Token',
|
||||
path: Routes.TOKEN,
|
||||
end: true,
|
||||
},
|
||||
{
|
||||
name: 'Supply & Vesting',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type ethers from 'ethers';
|
||||
import type { GetState, SetState } from 'zustand';
|
||||
import { create } from 'zustand';
|
||||
|
||||
export interface TxData {
|
||||
@@ -15,25 +16,27 @@ interface TransactionStore {
|
||||
remove: (tx: TxData) => void;
|
||||
}
|
||||
|
||||
export const useTransactionStore = create<TransactionStore>()((set, get) => ({
|
||||
transactions: [],
|
||||
add: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({ transactions: [...transactions, tx] });
|
||||
},
|
||||
update: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({
|
||||
transactions: [
|
||||
...transactions.filter((t) => t.tx.hash !== tx.tx.hash),
|
||||
tx,
|
||||
],
|
||||
});
|
||||
},
|
||||
remove: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({
|
||||
transactions: transactions.filter((t) => t.tx.hash !== tx.tx.hash),
|
||||
});
|
||||
},
|
||||
}));
|
||||
export const useTransactionStore = create(
|
||||
(set: SetState<TransactionStore>, get: GetState<TransactionStore>) => ({
|
||||
transactions: [],
|
||||
add: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({ transactions: [...transactions, tx] });
|
||||
},
|
||||
update: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({
|
||||
transactions: [
|
||||
...transactions.filter((t) => t.tx.hash !== tx.tx.hash),
|
||||
tx,
|
||||
],
|
||||
});
|
||||
},
|
||||
remove: (tx) => {
|
||||
const { transactions } = get();
|
||||
set({
|
||||
transactions: transactions.filter((t) => t.tx.hash !== tx.tx.hash),
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -17,10 +17,14 @@ describe('Desktop view', { tags: '@smoke' }, () => {
|
||||
|
||||
links.forEach((link, index) => {
|
||||
it(`${link} should be correctly rendered`, () => {
|
||||
cy.get('nav')
|
||||
.find(`a[data-testid=${link}]:visible`)
|
||||
cy.getByTestId('navbar')
|
||||
.find(`[data-testid="navbar-links"] a[data-testid=${link}]`)
|
||||
.then((element) => {
|
||||
cy.contains('Loading...').should('not.exist');
|
||||
cy.wrap(element).click();
|
||||
cy.wrap(element)
|
||||
.get('span.absolute.md\\:h-1.w-full')
|
||||
.should('exist');
|
||||
cy.location('hash').should('equal', hashes[index]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ export const Banner = () => {
|
||||
<Icon name="cross" className="w-6 h-6" ariaLabel="dismiss" />
|
||||
</button>
|
||||
<div>
|
||||
<span className="pr-4">Mainnet sim 3 is coming soon!</span>
|
||||
<span className="pr-4">Mainnet sim 2 is live!</span>
|
||||
<ExternalLink href="https://fairground.wtf/">Learn more</ExternalLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { NavLink, Link } from 'react-router-dom';
|
||||
import {
|
||||
DApp,
|
||||
NetworkSwitcher,
|
||||
@@ -9,22 +11,32 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
|
||||
import {
|
||||
Drawer,
|
||||
getNavLinkClassNames,
|
||||
getActiveNavLinkClassNames,
|
||||
Nav,
|
||||
NewTab,
|
||||
ThemeSwitcher,
|
||||
Navigation,
|
||||
NavigationList,
|
||||
NavigationItem,
|
||||
NavigationLink,
|
||||
ExternalLink,
|
||||
Icon,
|
||||
NavigationBreakpoint,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { Vega } from '../icons/vega';
|
||||
import type { HTMLAttributeAnchorTarget } from 'react';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
export const Navbar = ({
|
||||
theme = 'system',
|
||||
type NavbarTheme = 'inherit' | 'dark' | 'yellow';
|
||||
interface NavbarProps {
|
||||
navbarTheme?: NavbarTheme;
|
||||
}
|
||||
|
||||
const LinkList = ({
|
||||
navbarTheme,
|
||||
className = 'flex',
|
||||
dataTestId = 'navbar-links',
|
||||
onNavigate,
|
||||
}: {
|
||||
theme: ComponentProps<typeof Navigation>['theme'];
|
||||
navbarTheme: NavbarTheme;
|
||||
className?: string;
|
||||
dataTestId?: string;
|
||||
onNavigate?: () => void;
|
||||
}) => {
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const { marketId } = useGlobalStore((store) => ({
|
||||
@@ -34,67 +46,178 @@ export const Navbar = ({
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.MARKET]();
|
||||
return (
|
||||
<Navigation
|
||||
appName="Console"
|
||||
theme={theme}
|
||||
actions={
|
||||
<>
|
||||
<ThemeSwitcher />
|
||||
<VegaWalletConnectButton />
|
||||
</>
|
||||
}
|
||||
breakpoints={[521, 1067]}
|
||||
>
|
||||
<NavigationList
|
||||
className="[.drawer-content_&]:border-b [.drawer-content_&]:border-b-vega-light-200 dark:[.drawer-content_&]:border-b-vega-dark-200 [.drawer-content_&]:pb-8 [.drawer-content_&]:mb-2"
|
||||
hide={[NavigationBreakpoint.Small]}
|
||||
<div className={className} data-testid={dataTestId}>
|
||||
<AppNavLink
|
||||
name={t('Markets')}
|
||||
path={Links[Routes.MARKETS]()}
|
||||
navbarTheme={navbarTheme}
|
||||
onClick={onNavigate}
|
||||
end
|
||||
/>
|
||||
<AppNavLink
|
||||
name={t('Trading')}
|
||||
path={tradingPath}
|
||||
navbarTheme={navbarTheme}
|
||||
onClick={onNavigate}
|
||||
end
|
||||
/>
|
||||
<AppNavLink
|
||||
name={t('Portfolio')}
|
||||
path={Links[Routes.PORTFOLIO]()}
|
||||
navbarTheme={navbarTheme}
|
||||
onClick={onNavigate}
|
||||
/>
|
||||
<a
|
||||
href={tokenLink(TOKEN_GOVERNANCE)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={classNames(
|
||||
'w-full md:w-auto',
|
||||
getActiveNavLinkClassNames(false, navbarTheme)
|
||||
)}
|
||||
>
|
||||
<NavigationItem className="[.drawer-content_&]:w-full">
|
||||
<NetworkSwitcher className="[.drawer-content_&]:w-full" />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Narrow, NavigationBreakpoint.Small]}
|
||||
<span className="flex items-center justify-between w-full gap-2 pr-3 md:pr-0">
|
||||
{t('Governance')}
|
||||
<NewTab />
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MobileMenuBar = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => {
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [container, setContainer] = useState<HTMLElement | null>(null);
|
||||
|
||||
const menuButton = (
|
||||
<button
|
||||
className={classNames(
|
||||
'flex flex-col justify-around gap-3 p-2 relative z-30 h-[34px]',
|
||||
{
|
||||
'z-50': drawerOpen,
|
||||
}
|
||||
)}
|
||||
onClick={() => setDrawerOpen(!drawerOpen)}
|
||||
data-testid="button-menu-drawer"
|
||||
>
|
||||
<div
|
||||
className={classNames('w-[26px] h-[2px] transition-all', {
|
||||
'translate-y-0 rotate-0 bg-white': !drawerOpen,
|
||||
'bg-black': !drawerOpen && navbarTheme === 'yellow',
|
||||
'translate-y-[7.5px] rotate-45 bg-black dark:bg-white': drawerOpen,
|
||||
})}
|
||||
/>
|
||||
<div
|
||||
className={classNames('w-[26px] h-[2px] transition-all', {
|
||||
'translate-y-0 rotate-0 bg-white': !drawerOpen,
|
||||
'bg-black': !drawerOpen && navbarTheme === 'yellow',
|
||||
'-translate-y-[7.5px] -rotate-45 bg-black dark:bg-white': drawerOpen,
|
||||
})}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex overflow-hidden md:hidden" ref={setContainer}>
|
||||
<Drawer
|
||||
dataTestId="menu-drawer"
|
||||
open={drawerOpen}
|
||||
onChange={setDrawerOpen}
|
||||
container={container}
|
||||
trigger={menuButton}
|
||||
>
|
||||
<NavigationItem>
|
||||
<NavigationLink data-testid="Markets" to={Links[Routes.MARKETS]()}>
|
||||
{t('Markets')}
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink data-testid="Trading" to={tradingPath}>
|
||||
{t('Trading')}
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink
|
||||
data-testid="Portfolio"
|
||||
to={Links[Routes.PORTFOLIO]()}
|
||||
>
|
||||
{t('Portfolio')}
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<ExternalLink href={tokenLink(TOKEN_GOVERNANCE)}>
|
||||
<span className="flex items-center gap-2">
|
||||
<span>{t('Governance')}</span>{' '}
|
||||
<Icon name="arrow-top-right" size={3} />
|
||||
</span>
|
||||
</ExternalLink>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
className="[.drawer-content_&]:border-t [.drawer-content_&]:border-t-vega-light-200 dark:[.drawer-content_&]:border-t-vega-dark-200 [.drawer-content_&]:pt-8 [.drawer-content_&]:mt-4"
|
||||
hide={[
|
||||
NavigationBreakpoint.Small,
|
||||
NavigationBreakpoint.Narrow,
|
||||
NavigationBreakpoint.Full,
|
||||
]}
|
||||
>
|
||||
<NavigationItem className="[.drawer-content_&]:w-full text-black dark:text-white">
|
||||
<ThemeSwitcher withMobile />
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
</Navigation>
|
||||
<div className="border-l border-default px-4 py-2 gap-4 flex flex-col w-full h-full bg-white dark:bg-black dark:text-white justify-start">
|
||||
<div className="w-full h-1"></div>
|
||||
<div className="px-2 pt-10 w-full flex flex-col items-stretch">
|
||||
<NetworkSwitcher />
|
||||
<div className="w-full pt-8 h-1 border-b border-default"></div>
|
||||
</div>
|
||||
<LinkList
|
||||
className="flex flex-col"
|
||||
navbarTheme={navbarTheme}
|
||||
dataTestId="mobile-navbar-links"
|
||||
onNavigate={() => setDrawerOpen(false)}
|
||||
/>
|
||||
<div className="flex flex-col px-2 justify-between">
|
||||
<div className="w-full h-1 border-t border-default py-5"></div>
|
||||
<ThemeSwitcher withMobile />
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Navbar = ({ navbarTheme = 'inherit' }: NavbarProps) => {
|
||||
const titleContent = (
|
||||
<div className="hidden md:block">
|
||||
<NetworkSwitcher />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<Nav
|
||||
navbarTheme={navbarTheme}
|
||||
title={t('Console')}
|
||||
titleContent={titleContent}
|
||||
icon={
|
||||
<Link to="/">
|
||||
<Vega className="w-13" />
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<LinkList className="hidden md:flex md:px-2" navbarTheme={navbarTheme} />
|
||||
<div className="flex items-center gap-2 ml-auto overflow-hidden">
|
||||
<VegaWalletConnectButton />
|
||||
<ThemeSwitcher className="hidden md:block" />
|
||||
<MobileMenuBar navbarTheme={navbarTheme} />
|
||||
</div>
|
||||
</Nav>
|
||||
);
|
||||
};
|
||||
|
||||
interface AppNavLinkProps {
|
||||
name: string;
|
||||
path: string;
|
||||
navbarTheme: NavbarTheme;
|
||||
testId?: string;
|
||||
target?: HTMLAttributeAnchorTarget;
|
||||
end?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const AppNavLink = ({
|
||||
name,
|
||||
path,
|
||||
navbarTheme,
|
||||
target,
|
||||
testId = name,
|
||||
end,
|
||||
onClick,
|
||||
}: AppNavLinkProps) => {
|
||||
const borderClasses = classNames(
|
||||
'absolute h-[2px] md:h-1 w-full bottom-[-1px] left-0',
|
||||
{
|
||||
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
|
||||
'bg-black dark:bg-vega-yellow md:dark:bg-black': navbarTheme === 'yellow',
|
||||
}
|
||||
);
|
||||
return (
|
||||
<NavLink
|
||||
data-testid={testId}
|
||||
to={{ pathname: path }}
|
||||
className={getNavLinkClassNames(navbarTheme)}
|
||||
onClick={onClick}
|
||||
target={target}
|
||||
end={end}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
return (
|
||||
<>
|
||||
{name}
|
||||
{isActive && <span className={borderClasses} />}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -54,7 +54,7 @@ const MobileWalletButton = ({
|
||||
? 'hidden'
|
||||
: isYellow
|
||||
? 'fill-black'
|
||||
: 'fill-black dark:fill-white';
|
||||
: 'fill-white';
|
||||
const [container, setContainer] = useState<HTMLElement | null>(null);
|
||||
|
||||
const walletButton = (
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Head from 'next/head';
|
||||
import type { AppProps } from 'next/app';
|
||||
import { Navbar } from '../components/navbar';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
useEagerConnect as useVegaEagerConnect,
|
||||
@@ -32,7 +33,6 @@ import { ViewingBanner } from '../components/viewing-banner';
|
||||
import { Banner } from '../components/banner';
|
||||
import classNames from 'classnames';
|
||||
import { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||
import { Navbar } from '../components/navbar';
|
||||
|
||||
const DEFAULT_TITLE = t('Welcome to Vega trading!');
|
||||
|
||||
@@ -83,7 +83,9 @@ function AppBody({ Component }: AppProps) {
|
||||
</Head>
|
||||
<Title />
|
||||
<div className={gridClasses}>
|
||||
<Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'} />
|
||||
<Navbar
|
||||
navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'}
|
||||
/>
|
||||
<Banner />
|
||||
<ViewingBanner />
|
||||
<main data-testid={location.pathname}>
|
||||
|
||||
@@ -15,7 +15,7 @@ interface PageTitleStore {
|
||||
updateTitle: (title: string) => void;
|
||||
}
|
||||
|
||||
export const useGlobalStore = create<GlobalStore>()((set) => ({
|
||||
export const useGlobalStore = create<GlobalStore>((set) => ({
|
||||
nodeSwitcherDialog: false,
|
||||
marketId: LocalStorage.getItem('marketId') || null,
|
||||
shouldDisplayWelcomeDialog: false,
|
||||
|
||||
@@ -11,7 +11,7 @@ interface Actions {
|
||||
open: (open?: boolean) => void;
|
||||
}
|
||||
|
||||
export const useTransferDialog = create<State & Actions>()((set) => ({
|
||||
export const useTransferDialog = create<State & Actions>((set) => ({
|
||||
isOpen: false,
|
||||
open: (open = true) => {
|
||||
set(() => ({ isOpen: open }));
|
||||
|
||||
@@ -9,4 +9,4 @@ type HeaderStore = {
|
||||
[url: string]: HeaderEntry | undefined;
|
||||
};
|
||||
|
||||
export const useHeaderStore = create<HeaderStore>()(() => ({}));
|
||||
export const useHeaderStore = create<HeaderStore>(() => ({}));
|
||||
|
||||
@@ -20,7 +20,7 @@ export type AssetDetailsDialogStore = {
|
||||
open: (id: string, trigger?: HTMLElement | null, asJson?: boolean) => void;
|
||||
};
|
||||
|
||||
export const useAssetDetailsDialogStore = create<AssetDetailsDialogStore>()(
|
||||
export const useAssetDetailsDialogStore = create<AssetDetailsDialogStore>(
|
||||
(set) => ({
|
||||
isOpen: false,
|
||||
id: '',
|
||||
|
||||
@@ -19,7 +19,6 @@ import { addCreateMarket } from './lib/commands/create-market';
|
||||
import { addConnectPublicKey } from './lib/commands/add-connect-public-key';
|
||||
import { addVegaWalletSubmitProposal } from './lib/commands/vega-wallet-submit-proposal';
|
||||
import { addGetNodes } from './lib/commands/get-nodes';
|
||||
import { addVegaWalletSubmitLiquidityProvision } from './lib/commands/vega-wallet-submit-liquidity-provision';
|
||||
|
||||
addGetTestIdcommand();
|
||||
addSlackCommand();
|
||||
@@ -40,7 +39,6 @@ addMockTransactionResponse();
|
||||
addCreateMarket();
|
||||
addConnectPublicKey();
|
||||
addVegaWalletSubmitProposal();
|
||||
addVegaWalletSubmitLiquidityProvision();
|
||||
|
||||
export { mockConnectWallet } from './lib/commands/vega-wallet-connect';
|
||||
export type { onMessage } from './lib/mock-ws';
|
||||
|
||||
@@ -12,7 +12,7 @@ export const addConnectPublicKey = () => {
|
||||
Cypress.Commands.add('connectPublicKey', (publicKey) => {
|
||||
cy.getByTestId('connect-vega-wallet').then((connectWallet) => {
|
||||
if (connectWallet.length) {
|
||||
cy.get('aside [data-testid="connect-vega-wallet"]').click();
|
||||
cy.getByTestId('connect-vega-wallet').click();
|
||||
cy.getByTestId('connector-view').should('be.visible').click();
|
||||
cy.getByTestId('address').click();
|
||||
cy.getByTestId('address').type(publicKey);
|
||||
|
||||
@@ -25,7 +25,7 @@ export function addVegaWalletConnect() {
|
||||
mockConnectWallet();
|
||||
cy.highlight(`Connecting Vega Wallet`);
|
||||
cy.get(
|
||||
`[data-testid=connect-vega-wallet${isMobile ? '-mobile' : ''}]:visible`
|
||||
`[data-testid=connect-vega-wallet${isMobile ? '-mobile' : ''}]`
|
||||
).click();
|
||||
cy.get('[data-testid=connectors-list]')
|
||||
.find('[data-testid="connector-jsonRpc"]')
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { PeggedReference } from '@vegaprotocol/types';
|
||||
import type { LiquidityProvisionSubmission } from '@vegaprotocol/wallet';
|
||||
import { createWalletClient, sendVegaTx } from '../capsule/wallet-client';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
VegaWalletSubmitLiquidityProvision(
|
||||
marketId: string,
|
||||
amount: string
|
||||
): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const addVegaWalletSubmitLiquidityProvision = () => {
|
||||
Cypress.Commands.add(
|
||||
'VegaWalletSubmitLiquidityProvision',
|
||||
(marketId, amount) => {
|
||||
const vegaWalletUrl = Cypress.env('VEGA_WALLET_URL');
|
||||
const token = Cypress.env('VEGA_WALLET_API_TOKEN');
|
||||
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
|
||||
|
||||
const liquidityProvisionTx: LiquidityProvisionSubmission = {
|
||||
liquidityProvisionSubmission: {
|
||||
marketId: marketId,
|
||||
commitmentAmount: amount,
|
||||
fee: '0.001',
|
||||
buys: [
|
||||
{
|
||||
offset: '10',
|
||||
proportion: '1',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
{
|
||||
offset: '12',
|
||||
proportion: '2',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
],
|
||||
sells: [
|
||||
{
|
||||
offset: '10',
|
||||
proportion: '2',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
{
|
||||
offset: '12',
|
||||
proportion: '2',
|
||||
reference: PeggedReference.PEGGED_REFERENCE_MID,
|
||||
},
|
||||
],
|
||||
},
|
||||
pubKey: vegaPubKey,
|
||||
propagate: true,
|
||||
};
|
||||
|
||||
createWalletClient(vegaWalletUrl, token);
|
||||
|
||||
cy.highlight('Submitting liquidity provision');
|
||||
|
||||
sendVegaTx(vegaPubKey, liquidityProvisionTx);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -184,9 +184,7 @@ export const DealTicket = ({
|
||||
type,
|
||||
// when changing type also update the tif to what was last used of new type
|
||||
timeInForce: lastTIF[type] || order.timeInForce,
|
||||
expiresAt: undefined,
|
||||
});
|
||||
clearErrors('expiresAt');
|
||||
}}
|
||||
market={market}
|
||||
marketData={marketData}
|
||||
@@ -234,12 +232,7 @@ export const DealTicket = ({
|
||||
update({ timeInForce });
|
||||
// Set tif value for the given order type, so that when switching
|
||||
// types we know the last used TIF for the given order type
|
||||
setLastTIF((curr) => ({
|
||||
...curr,
|
||||
[order.type]: timeInForce,
|
||||
expiresAt: undefined,
|
||||
}));
|
||||
clearErrors('expiresAt');
|
||||
setLastTIF((curr) => ({ ...curr, [order.type]: timeInForce }));
|
||||
}}
|
||||
market={market}
|
||||
marketData={marketData}
|
||||
|
||||
@@ -13,7 +13,7 @@ export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t(
|
||||
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance');
|
||||
export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) =>
|
||||
t(
|
||||
"The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset (%s).",
|
||||
"The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset ($s).",
|
||||
[settlementAsset]
|
||||
);
|
||||
export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t(
|
||||
|
||||
@@ -11,6 +11,8 @@ import { useMemo } from 'react';
|
||||
import type { Market, MarketData } from '@vegaprotocol/market-list';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
EST_CLOSEOUT_TOOLTIP_TEXT,
|
||||
// EST_MARGIN_TOOLTIP_TEXT,
|
||||
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
||||
NOTIONAL_SIZE_TOOLTIP_TEXT,
|
||||
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||
@@ -98,13 +100,16 @@ export interface FeeDetails {
|
||||
export const getFeeDetailsValues = ({
|
||||
balance,
|
||||
assetSymbol,
|
||||
estCloseOut,
|
||||
estimateOrder,
|
||||
margin,
|
||||
market,
|
||||
notionalSize,
|
||||
totalMargin,
|
||||
}: FeeDetails) => {
|
||||
const assetDecimals =
|
||||
market.tradableInstrument.instrument.product.settlementAsset.decimals;
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
const formatValueWithMarketDp = (
|
||||
value: string | number | null | undefined
|
||||
): string => {
|
||||
@@ -188,5 +193,11 @@ export const getFeeDetailsValues = ({
|
||||
symbol: assetSymbol,
|
||||
labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||
});
|
||||
details.push({
|
||||
label: t('Liquidation'),
|
||||
value: estCloseOut && `~${formatValueWithMarketDp(estCloseOut)}`,
|
||||
symbol: market.tradableInstrument.instrument.product.quoteName,
|
||||
labelDescription: EST_CLOSEOUT_TOOLTIP_TEXT(quoteName),
|
||||
});
|
||||
return details;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ interface Actions {
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export const useDepositDialog = create<State & Actions>()((set) => ({
|
||||
export const useDepositDialog = create<State & Actions>((set) => ({
|
||||
isOpen: false,
|
||||
assetId: undefined,
|
||||
open: (assetId) => set(() => ({ assetId, isOpen: true })),
|
||||
|
||||
@@ -392,7 +392,7 @@ const FormButton = ({ approved, selectedAsset }: FormButtonProps) => {
|
||||
data-testid="deposit-submit"
|
||||
variant={isActive ? 'primary' : 'default'}
|
||||
fill={true}
|
||||
disabled={invalidChain}
|
||||
disabled={invalidChain || (selectedAsset && !approved)}
|
||||
>
|
||||
{t('Deposit')}
|
||||
</Button>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ExternalLink, Intent, Notification } from '@vegaprotocol/ui-toolkit';
|
||||
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
|
||||
import { getFaucetError } from './get-faucet-error';
|
||||
|
||||
interface FaucetNotificationProps {
|
||||
isActive: boolean;
|
||||
@@ -37,13 +36,13 @@ export const FaucetNotification = ({
|
||||
}
|
||||
|
||||
if (tx.status === EthTxStatus.Error) {
|
||||
const errorMessage = getFaucetError(tx.error, selectedAsset.symbol);
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<Notification
|
||||
intent={Intent.Danger}
|
||||
testId="faucet-error"
|
||||
message={errorMessage}
|
||||
// @ts-ignore tx.error not typed correctly
|
||||
message={t(`Faucet failed: ${tx.error?.reason}`)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -56,7 +55,7 @@ export const FaucetNotification = ({
|
||||
intent={Intent.Warning}
|
||||
testId="faucet-requested"
|
||||
message={t(
|
||||
`Confirm the transaction in your Ethereum wallet to use the ${selectedAsset?.symbol} faucet`
|
||||
`Go to your Ethereum wallet and approve the faucet transaction for ${selectedAsset?.symbol}`
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -70,21 +69,14 @@ export const FaucetNotification = ({
|
||||
intent={Intent.Primary}
|
||||
testId="faucet-pending"
|
||||
message={
|
||||
<>
|
||||
<p className="mb-2">
|
||||
{t(
|
||||
'Your request for funds from the %s faucet is being confirmed by the Ethereum network',
|
||||
selectedAsset.symbol
|
||||
)}{' '}
|
||||
</p>
|
||||
<p>
|
||||
{t('Faucet pending...')}{' '}
|
||||
{tx.txHash && (
|
||||
<p>
|
||||
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
|
||||
{t('View on Etherscan')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
|
||||
{t('View on Etherscan')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -98,21 +90,14 @@ export const FaucetNotification = ({
|
||||
intent={Intent.Success}
|
||||
testId="faucet-confirmed"
|
||||
message={
|
||||
<>
|
||||
<p className="mb-2">
|
||||
{t(
|
||||
'%s has been deposited in your Ethereum wallet',
|
||||
selectedAsset.symbol
|
||||
)}{' '}
|
||||
</p>
|
||||
<p>
|
||||
{t('Faucet successful')}{' '}
|
||||
{tx.txHash && (
|
||||
<p>
|
||||
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
|
||||
{t('View on Etherscan')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
<ExternalLink href={`${ETHERSCAN_URL}/tx/${tx.txHash}`}>
|
||||
{t('View on Etherscan')}
|
||||
</ExternalLink>
|
||||
)}
|
||||
</>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { TxError } from '@vegaprotocol/web3';
|
||||
|
||||
export const getFaucetError = (error: TxError | null, symbol: string) => {
|
||||
const reasonMap: {
|
||||
[reason: string]: string;
|
||||
} = {
|
||||
'faucet not enabled': t(
|
||||
'The %s faucet is not available at this time',
|
||||
symbol
|
||||
),
|
||||
'must wait faucetCallLimit between faucet calls': t(
|
||||
'You have exceeded the maximum number of faucet attempts allowed'
|
||||
),
|
||||
'user rejected transaction': t(
|
||||
'The faucet transaction was rejected by the connected Ethereum wallet'
|
||||
),
|
||||
};
|
||||
// render a customized failure message from the map above or fallback
|
||||
// to a non generic error message
|
||||
return error && 'reason' in error && reasonMap[error.reason]
|
||||
? reasonMap[error.reason]
|
||||
: t('Faucet of %s failed', symbol);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { act } from 'react-dom/test-utils';
|
||||
const zu = jest.requireActual('zustand'); // if using jest
|
||||
|
||||
// a variable to hold reset functions for all stores declared in the app
|
||||
const storeResetFns = new Set();
|
||||
|
||||
// when creating a store, we get its initial state, create a reset function and add it in the set
|
||||
export const create = (createState) => {
|
||||
const store = zu.create(createState);
|
||||
const initialState = store.getState();
|
||||
storeResetFns.add(() => store.setState(initialState, true));
|
||||
return store;
|
||||
};
|
||||
|
||||
// Reset all stores after each test run
|
||||
beforeEach(() => {
|
||||
act(() => storeResetFns.forEach((resetFn) => resetFn()));
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { StateCreator } from 'zustand';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
const { create: actualCreate } = jest.requireActual('zustand'); // if using jest
|
||||
|
||||
// a variable to hold reset functions for all stores declared in the app
|
||||
const storeResetFns = new Set<() => void>();
|
||||
|
||||
// when creating a store, we get its initial state, create a reset function and add it in the set
|
||||
export const create =
|
||||
() =>
|
||||
<S>(createState: StateCreator<S>) => {
|
||||
const store = actualCreate(createState);
|
||||
const initialState = store.getState();
|
||||
storeResetFns.add(() => store.setState(initialState, true));
|
||||
return store;
|
||||
};
|
||||
|
||||
// Reset all stores after each test run
|
||||
beforeEach(() => {
|
||||
act(() => storeResetFns.forEach((resetFn) => resetFn()));
|
||||
});
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import { useEnvironment } from '../../hooks/use-environment';
|
||||
import { Networks } from '../../types';
|
||||
import { DApp, TOKEN_NEW_NETWORK_PARAM_PROPOSAL, useLinks } from '../../hooks';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const envNameMapping: Record<Networks, string> = {
|
||||
[Networks.VALIDATOR_TESTNET]: t('VALIDATOR_TESTNET'),
|
||||
@@ -81,17 +80,7 @@ const NetworkLabel = ({
|
||||
</span>
|
||||
);
|
||||
|
||||
type NetworkSwitcherProps = {
|
||||
/**
|
||||
* The current network identifier, defaults to the `VEGA_ENV` if unset.
|
||||
*/
|
||||
currentNetwork?: Networks;
|
||||
className?: string;
|
||||
};
|
||||
export const NetworkSwitcher = ({
|
||||
currentNetwork,
|
||||
className,
|
||||
}: NetworkSwitcherProps) => {
|
||||
export const NetworkSwitcher = () => {
|
||||
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
@@ -108,8 +97,6 @@ export const NetworkSwitcher = ({
|
||||
);
|
||||
const menuRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const current = currentNetwork || VEGA_ENV;
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
open={isOpen}
|
||||
@@ -117,9 +104,9 @@ export const NetworkSwitcher = ({
|
||||
trigger={
|
||||
<DropdownMenuTrigger
|
||||
ref={menuRef}
|
||||
className={classNames('flex justify-between items-center', className)}
|
||||
className="flex justify-between items-center"
|
||||
>
|
||||
{envTriggerMapping[current]}
|
||||
{envTriggerMapping[VEGA_ENV]}
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
@@ -138,7 +125,7 @@ export const NetworkSwitcher = ({
|
||||
<a href={VEGA_NETWORKS[key]}>
|
||||
{envNameMapping[key]}
|
||||
<NetworkLabel
|
||||
isCurrent={current === key}
|
||||
isCurrent={VEGA_ENV === key}
|
||||
isAvailable={!!VEGA_NETWORKS[key]}
|
||||
/>
|
||||
</a>
|
||||
@@ -163,7 +150,7 @@ export const NetworkSwitcher = ({
|
||||
<div className="mr-4">
|
||||
<Link href={VEGA_NETWORKS[key]}>{envNameMapping[key]}</Link>
|
||||
<NetworkLabel
|
||||
isCurrent={current === key}
|
||||
isCurrent={VEGA_ENV === key}
|
||||
isAvailable={!!VEGA_NETWORKS[key]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@ export type EnvStore = Env & Actions;
|
||||
export const STORAGE_KEY = 'vega_url';
|
||||
const SUBSCRIPTION_TIMEOUT = 3000;
|
||||
|
||||
export const useEnvironment = create<EnvStore>()((set, get) => ({
|
||||
export const useEnvironment = create<EnvStore>((set, get) => ({
|
||||
...compileEnvVars(),
|
||||
nodes: [],
|
||||
status: 'default',
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx",
|
||||
"jest.config.ts",
|
||||
"__mocks__"
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
|
||||
@@ -165,6 +165,5 @@ module.exports = {
|
||||
},
|
||||
data: {
|
||||
selected: 'state~="checked"',
|
||||
open: 'state~="open"',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
open: false,
|
||||
children: (
|
||||
<p className="h-full bg-white dark:bg-black text-black dark:text-white">
|
||||
<p className="h-full bg-black dark:bg-white text-white dark:text-black">
|
||||
Some content
|
||||
</p>
|
||||
),
|
||||
|
||||
@@ -27,12 +27,11 @@ export function Drawer({
|
||||
trigger,
|
||||
}: DrawerProps) {
|
||||
const contentClasses = classNames(
|
||||
'group/drawer',
|
||||
'h-full max-w-[500px] w-[90vw] z-10 top-0 right-0 fixed transition-transform',
|
||||
className,
|
||||
{
|
||||
'translate-x-[100%]': !open,
|
||||
'translate-x-0 z-20': open,
|
||||
'translate-x-0 z-40': open,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export const DropdownMenuContent = forwardRef<
|
||||
<DropdownMenuPrimitive.Content
|
||||
{...contentProps}
|
||||
ref={forwardedRef}
|
||||
className="min-w-[290px] bg-neutral-200 dark:bg-white p-2 rounded z-20"
|
||||
className="min-w-[290px] bg-neutral-200 dark:bg-white p-2 rounded text-black"
|
||||
align="start"
|
||||
sideOffset={10}
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export * from './accordion';
|
||||
export * from './announcement-banner';
|
||||
export * from './arrows';
|
||||
export * from './async-renderer';
|
||||
export * from './background-video';
|
||||
export * from './announcement-banner';
|
||||
export * from './button';
|
||||
export * from './callout';
|
||||
export * from './checkbox';
|
||||
@@ -19,16 +19,12 @@ export * from './key-value-table';
|
||||
export * from './link';
|
||||
export * from './loader';
|
||||
export * from './lozenge';
|
||||
export * from './maintenance-page';
|
||||
export * from './nav-dropdown';
|
||||
export * from './nav';
|
||||
export * from './navigation';
|
||||
export * from './notification';
|
||||
export * from './nav-dropdown';
|
||||
export * from './popover';
|
||||
export * from './progress-bar';
|
||||
export * from './radio-group';
|
||||
export * from './resizable-grid';
|
||||
export * from './rounded-wrapper';
|
||||
export * from './select';
|
||||
export * from './simple-grid';
|
||||
export * from './slider';
|
||||
@@ -39,10 +35,13 @@ export * from './tabs';
|
||||
export * from './text-area';
|
||||
export * from './theme-switcher';
|
||||
export * from './thumbs';
|
||||
export * from './toast';
|
||||
export * from './toggle';
|
||||
export * from './tooltip';
|
||||
export * from './traffic-light';
|
||||
export * from './vega-icons';
|
||||
export * from './vega-logo';
|
||||
export * from './viewing-as-user';
|
||||
export * from './traffic-light';
|
||||
export * from './toast';
|
||||
export * from './notification';
|
||||
export * from './rounded-wrapper';
|
||||
export * from './maintenance-page';
|
||||
|
||||
@@ -13,7 +13,6 @@ export const InputError = ({
|
||||
children,
|
||||
forInput,
|
||||
testId,
|
||||
className,
|
||||
...props
|
||||
}: InputErrorProps) => {
|
||||
const effectiveClassName = classNames(
|
||||
@@ -32,7 +31,7 @@ export const InputError = ({
|
||||
<div
|
||||
data-testid={testId || 'input-error-text'}
|
||||
aria-describedby={forInput}
|
||||
className={classNames(effectiveClassName, className)}
|
||||
className={effectiveClassName}
|
||||
{...props}
|
||||
role="alert"
|
||||
>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './navigation';
|
||||
export * from './navigation-utils';
|
||||
export * from './navigation-drawer';
|
||||
@@ -1,136 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties, HTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { createContext } from 'react';
|
||||
import { create } from 'zustand';
|
||||
import type { NavigationProps } from './navigation-utils';
|
||||
|
||||
export const NavigationDrawerContext = createContext<true | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
type NavigationDrawerStore = {
|
||||
drawerOpen: boolean;
|
||||
setDrawerOpen: (isOpen: boolean) => void;
|
||||
};
|
||||
export const useNavigationDrawer = create<NavigationDrawerStore>((set) => ({
|
||||
drawerOpen: false,
|
||||
setDrawerOpen: (isOpen) => {
|
||||
set({ drawerOpen: isOpen });
|
||||
},
|
||||
}));
|
||||
|
||||
export const BurgerIcon = ({
|
||||
variant = 'burger',
|
||||
className,
|
||||
}: { variant?: 'burger' | 'close' } & HTMLAttributes<SVGAElement>) => (
|
||||
<svg
|
||||
className={classNames('stroke-[1px] transition-transform', className)}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<line
|
||||
x1={0.5}
|
||||
x2={15.5}
|
||||
y1={3.5}
|
||||
y2={3.5}
|
||||
className={classNames('transition-transform duration-75', {
|
||||
'rotate-45 translate-y-[4px] origin-[8px_3.5px]': variant === 'close',
|
||||
})}
|
||||
/>
|
||||
<line
|
||||
x1={0.5}
|
||||
x2={15.5}
|
||||
y1={11.5}
|
||||
y2={11.5}
|
||||
className={classNames('transition-transform duration-75', {
|
||||
'rotate-[-45deg] translate-y-[-4px] origin-[8px_11.5px]':
|
||||
variant === 'close',
|
||||
})}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const NavigationDrawerTrigger = forwardRef<
|
||||
HTMLButtonElement,
|
||||
Pick<NavigationProps, 'theme'>
|
||||
>(({ theme }, ref) => {
|
||||
const { drawerOpen, setDrawerOpen } = useNavigationDrawer((state) => ({
|
||||
drawerOpen: state.drawerOpen,
|
||||
setDrawerOpen: state.setDrawerOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<button
|
||||
data-testid="button-menu-drawer"
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
'px-2',
|
||||
`hidden group-[.nav-size-narrow]:block group-[.nav-size-small]:block`,
|
||||
{
|
||||
'z-[21]': drawerOpen,
|
||||
}
|
||||
)}
|
||||
onClick={() => {
|
||||
setDrawerOpen(!drawerOpen);
|
||||
}}
|
||||
>
|
||||
<BurgerIcon
|
||||
className={classNames({
|
||||
'stroke-black dark:stroke-white': theme === 'system',
|
||||
'stroke-black': theme === 'light' || theme === 'yellow',
|
||||
'stroke-white': theme === 'dark',
|
||||
'dark:stroke-white': drawerOpen && theme === 'yellow',
|
||||
})}
|
||||
variant={drawerOpen ? 'close' : 'burger'}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
export const NavigationDrawerContent = ({
|
||||
theme,
|
||||
children,
|
||||
style,
|
||||
}: { style?: CSSProperties } & Pick<NavigationProps, 'theme' | 'children'>) => {
|
||||
return (
|
||||
<NavigationDrawerContext.Provider value={true}>
|
||||
<div
|
||||
className={classNames(
|
||||
'drawer-content',
|
||||
'border-l h-full relative overflow-auto',
|
||||
'px-4 pb-8 font-alpha',
|
||||
// text
|
||||
{
|
||||
'text-vega-light-300 dark:text-vega-dark-300':
|
||||
theme === 'system' || theme === 'yellow',
|
||||
'text-vega-light-300': theme === 'light',
|
||||
'text-vega-dark-300': theme === 'dark',
|
||||
},
|
||||
// border
|
||||
{
|
||||
'border-l-vega-light-200 dark:border-l-vega-dark-200':
|
||||
theme === 'system' || theme === 'yellow',
|
||||
'border-l-vega-light-200': theme === 'light',
|
||||
'border-l-vega-dark-200': theme === 'dark',
|
||||
},
|
||||
// background
|
||||
{
|
||||
'bg-white dark:bg-black': theme === 'system' || theme === 'yellow',
|
||||
'bg-white': theme === 'light',
|
||||
'bg-black': theme === 'dark',
|
||||
}
|
||||
)}
|
||||
style={style}
|
||||
>
|
||||
<div
|
||||
data-testid="menu-drawer"
|
||||
className="flex flex-col gap-2 pr-10 text-lg"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</NavigationDrawerContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type NavigationProps = {
|
||||
/**
|
||||
* The display name of the dApp, e.g. "Console", "Explorer"
|
||||
*/
|
||||
appName: string;
|
||||
/**
|
||||
* URL pointing to the home page.
|
||||
*/
|
||||
homeLink?: string;
|
||||
/**
|
||||
* The theme of the navigation.
|
||||
* @default "system"
|
||||
*/
|
||||
theme: 'system' | 'light' | 'dark' | 'yellow';
|
||||
/**
|
||||
* The navigation items (links, buttons, dropdowns, etc.)
|
||||
*/
|
||||
children?: ReactNode;
|
||||
/**
|
||||
* The navigation actions (e.g. theme switcher, wallet connector)
|
||||
*/
|
||||
actions?: ReactNode;
|
||||
/**
|
||||
* Size variants breakpoints
|
||||
*/
|
||||
breakpoints?: [number, number];
|
||||
fullWidth?: boolean;
|
||||
onResize?: (width: number, navigationElement: HTMLElement) => void;
|
||||
};
|
||||
|
||||
export enum NavigationBreakpoint {
|
||||
/**
|
||||
* Full width navigation
|
||||
* `width > breakpoints[1]`
|
||||
*/
|
||||
Full = 'nav-size-full',
|
||||
/**
|
||||
* Narrow variant
|
||||
* `width > breakpoints[0] && width <= breakpoints[1]`
|
||||
*/
|
||||
Narrow = 'nav-size-narrow',
|
||||
/**
|
||||
* Small variant
|
||||
* `width <= breakpoints[0]`
|
||||
*/
|
||||
Small = 'nav-size-small',
|
||||
}
|
||||
|
||||
export type NavigationElementProps = {
|
||||
hide?: NavigationBreakpoint[] | true;
|
||||
hideInDrawer?: boolean;
|
||||
};
|
||||
|
||||
export const NavigationContext = createContext<{
|
||||
theme: NavigationProps['theme'];
|
||||
}>({ theme: 'system' });
|
||||
|
||||
export const setSizeVariantClasses = (
|
||||
breakpoints: [number, number],
|
||||
currentWidth: number,
|
||||
target: HTMLElement
|
||||
) => {
|
||||
if (
|
||||
currentWidth <= breakpoints[0] &&
|
||||
!target.classList.contains(NavigationBreakpoint.Small)
|
||||
) {
|
||||
target.classList.remove(
|
||||
NavigationBreakpoint.Full,
|
||||
NavigationBreakpoint.Narrow
|
||||
);
|
||||
target.classList.add(NavigationBreakpoint.Small);
|
||||
}
|
||||
if (
|
||||
currentWidth > breakpoints[0] &&
|
||||
currentWidth <= breakpoints[1] &&
|
||||
!target.classList.contains(NavigationBreakpoint.Narrow)
|
||||
) {
|
||||
target.classList.remove(
|
||||
NavigationBreakpoint.Full,
|
||||
NavigationBreakpoint.Small
|
||||
);
|
||||
target.classList.add(NavigationBreakpoint.Narrow);
|
||||
}
|
||||
if (
|
||||
currentWidth > breakpoints[1] &&
|
||||
!target.classList.contains(NavigationBreakpoint.Full)
|
||||
) {
|
||||
target.classList.remove(
|
||||
NavigationBreakpoint.Narrow,
|
||||
NavigationBreakpoint.Small
|
||||
);
|
||||
target.classList.add(NavigationBreakpoint.Full);
|
||||
}
|
||||
};
|
||||
@@ -1,251 +0,0 @@
|
||||
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { MemoryRouter, Route, Routes, useMatch } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
ExternalLink,
|
||||
Icon,
|
||||
NavigationBreakpoint,
|
||||
ThemeSwitcher,
|
||||
} from '..';
|
||||
import {
|
||||
Navigation,
|
||||
NavigationContent,
|
||||
NavigationItem,
|
||||
NavigationLink,
|
||||
NavigationList,
|
||||
NavigationTrigger,
|
||||
} from './navigation';
|
||||
|
||||
export default {
|
||||
title: 'Navigation',
|
||||
component: Navigation,
|
||||
} as ComponentMeta<typeof Navigation>;
|
||||
|
||||
const Template: ComponentStory<typeof Navigation> = ({
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const nav = <Navigation {...props}>{children}</Navigation>;
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<div className="h-[300px]">
|
||||
{nav}
|
||||
<div className="mt-2">
|
||||
<Routes>
|
||||
<Route path="transactions" element={<h1>Transactions</h1>} />
|
||||
<Route path="blocks" element={<h1>Blocks</h1>} />
|
||||
<Route path="markets/all" element={<h1>All markets</h1>} />
|
||||
</Routes>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis
|
||||
pariatur a nemo quos sed! Voluptas itaque voluptate dolores minima.
|
||||
Iste laudantium perspiciatis accusamus facere eius repudiandae sit
|
||||
odio saepe nisi.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Ea facere
|
||||
ab at incidunt numquam nemo natus eos iure, iste tenetur illo
|
||||
dolores, commodi magni quam dolor totam quae velit eaque.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quaerat
|
||||
minus perspiciatis quas temporibus odit? Enim ipsam nisi amet
|
||||
molestias magnam esse blanditiis aperiam sapiente quaerat. Veniam
|
||||
unde magnam exercitationem distinctio.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
appName: '',
|
||||
};
|
||||
|
||||
const ExplorerNav = () => {
|
||||
const isOnMarkets = useMatch('markets/*');
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavigationList>
|
||||
<NavigationItem hide={[NavigationBreakpoint.Small]}>
|
||||
{/* <NetworkSwitcher currentNetwork={Networks.CUSTOM} /> */}
|
||||
<Button size="xs">Network Switcher</Button>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Narrow, NavigationBreakpoint.Small]}
|
||||
>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="transactions">Transactions</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="blocks">Blocks</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationTrigger isActive={Boolean(isOnMarkets)}>
|
||||
Markets
|
||||
</NavigationTrigger>
|
||||
<NavigationContent>
|
||||
<NavigationList>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="markets/all">All markets</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="markets/proposed">
|
||||
Proposed markets
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="markets/failed">
|
||||
Failed markets
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
</NavigationContent>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="validators">Validators</NavigationLink>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Explorer = Template.bind({});
|
||||
Explorer.args = {
|
||||
appName: 'Explorer',
|
||||
theme: 'system',
|
||||
children: <ExplorerNav />,
|
||||
actions: (
|
||||
<>
|
||||
<ThemeSwitcher />
|
||||
{/* JUST A PLACEHOLDER */}
|
||||
<div className="border rounded px-2 py-1 text-xs font-alpha w-60 flex items-center gap-1">
|
||||
<Icon
|
||||
name="search"
|
||||
size={3}
|
||||
className="text-vega-light-200 dark:text-vega-dark-200"
|
||||
/>
|
||||
<input
|
||||
className="w-full bg-transparent outline-none"
|
||||
placeholder="Enter block number or transaction hash"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
const ConsoleNav = () => {
|
||||
return (
|
||||
<>
|
||||
<NavigationList>
|
||||
<NavigationItem>
|
||||
<Button size="xs">Network Switcher</Button>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Narrow, NavigationBreakpoint.Small]}
|
||||
>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="markets">Markets</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="trading">Trading</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="portfolio">Portfolio</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<ExternalLink>
|
||||
<span className="flex items-center gap-2">
|
||||
<span>Governance</span> <Icon name="arrow-top-right" size={3} />
|
||||
</span>
|
||||
</ExternalLink>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Console = Template.bind({});
|
||||
Console.args = {
|
||||
appName: 'Console',
|
||||
theme: 'system',
|
||||
children: <ConsoleNav />,
|
||||
breakpoints: [478, 770],
|
||||
actions: <ThemeSwitcher />,
|
||||
};
|
||||
|
||||
const GovernanceNav = () => {
|
||||
const isOnToken = useMatch('token/*');
|
||||
return (
|
||||
<>
|
||||
<NavigationList>
|
||||
<NavigationItem hide={[NavigationBreakpoint.Small]}>
|
||||
<Button size="xs">Network Switcher</Button>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
<NavigationList
|
||||
hide={[NavigationBreakpoint.Narrow, NavigationBreakpoint.Small]}
|
||||
>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="proposals">Proposals</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="validators">Validators</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="rewards">Rewards</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationTrigger isActive={Boolean(isOnToken)}>
|
||||
Token
|
||||
</NavigationTrigger>
|
||||
<NavigationContent>
|
||||
<NavigationList>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="token/index">Token</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="token/tranches">
|
||||
Supply & Vesting
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="token/withdraw">Withdraw</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="token/redeem">Redeem</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="token/associate">Associate</NavigationLink>
|
||||
</NavigationItem>
|
||||
<NavigationItem>
|
||||
<NavigationLink to="token/disassociate">
|
||||
Disassociate
|
||||
</NavigationLink>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
</NavigationContent>
|
||||
</NavigationItem>
|
||||
</NavigationList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Governance = Template.bind({});
|
||||
Governance.args = {
|
||||
appName: 'Governance',
|
||||
theme: 'dark',
|
||||
children: <GovernanceNav />,
|
||||
actions: (
|
||||
<Button size="sm">
|
||||
<span className="flex items-center gap-2">
|
||||
<span>Connect</span> <Icon name="arrow-right" size={3} />
|
||||
</span>
|
||||
</Button>
|
||||
),
|
||||
};
|
||||
@@ -1,388 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import type { ComponentProps, ReactNode } from 'react';
|
||||
import { useLayoutEffect } from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { VegaLogo } from '../vega-logo';
|
||||
import * as NavigationMenu from '@radix-ui/react-navigation-menu';
|
||||
import { Icon } from '../icon';
|
||||
import { Drawer } from '../drawer';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import type {
|
||||
NavigationElementProps,
|
||||
NavigationProps,
|
||||
} from './navigation-utils';
|
||||
import { setSizeVariantClasses } from './navigation-utils';
|
||||
import { NavigationBreakpoint, NavigationContext } from './navigation-utils';
|
||||
import {
|
||||
NavigationDrawerTrigger,
|
||||
NavigationDrawerContext,
|
||||
useNavigationDrawer,
|
||||
NavigationDrawerContent,
|
||||
} from './navigation-drawer';
|
||||
|
||||
const Logo = ({
|
||||
appName,
|
||||
homeLink,
|
||||
}: Pick<NavigationProps, 'theme' | 'appName' | 'homeLink'>) => {
|
||||
const { theme } = useContext(NavigationContext);
|
||||
return (
|
||||
<div className="flex h-full gap-4 items-center">
|
||||
{homeLink ? (
|
||||
<NavLink to={homeLink}>
|
||||
<VegaLogo className="h-4 group-[.nav-size-small]:h-3" />
|
||||
</NavLink>
|
||||
) : (
|
||||
<VegaLogo className="h-4 group-[.nav-size-small]:h-3" />
|
||||
)}
|
||||
|
||||
{appName && (
|
||||
<span
|
||||
data-testid="nav-app-name"
|
||||
className={classNames(
|
||||
'group-[.nav-size-small]:text-sm',
|
||||
'font-alpha calt lowercase text-xl tracking-[1px] whitespace-nowrap leading-1',
|
||||
'border-l pl-4',
|
||||
{
|
||||
'border-l-vega-light-200 dark:border-l-vega-dark-200':
|
||||
theme === 'system',
|
||||
'border-l-vega-light-200': theme === 'light',
|
||||
'border-l-vega-dark-200': theme === 'dark' || theme === 'yellow',
|
||||
}
|
||||
)}
|
||||
>
|
||||
{appName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const determineIfHidden = ({ hide, hideInDrawer }: NavigationElementProps) => [
|
||||
{
|
||||
'[.nav-size-full_.navbar_&]:hidden':
|
||||
Array.isArray(hide) && hide?.includes(NavigationBreakpoint.Full),
|
||||
'[.nav-size-narrow_.navbar_&]:hidden':
|
||||
Array.isArray(hide) && hide?.includes(NavigationBreakpoint.Narrow),
|
||||
'[.nav-size-small_.navbar_&]:hidden':
|
||||
Array.isArray(hide) && hide?.includes(NavigationBreakpoint.Small),
|
||||
'[.drawer-content_&]:hidden': hideInDrawer,
|
||||
},
|
||||
];
|
||||
|
||||
const Spacer = () => <div className="w-full" aria-hidden="true"></div>;
|
||||
|
||||
export const NavigationItem = ({
|
||||
children,
|
||||
className,
|
||||
hide,
|
||||
hideInDrawer,
|
||||
...props
|
||||
}: ComponentProps<typeof NavigationMenu.Item> & NavigationElementProps) => {
|
||||
const insideDrawer = useContext(NavigationDrawerContext);
|
||||
return (
|
||||
<NavigationMenu.Item
|
||||
className={classNames(
|
||||
!insideDrawer && ['h-12 [.navigation-content_&]:h-8 flex items-center'],
|
||||
determineIfHidden({ hide, hideInDrawer }),
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</NavigationMenu.Item>
|
||||
);
|
||||
};
|
||||
export const NavigationList = ({
|
||||
children,
|
||||
className,
|
||||
hide,
|
||||
hideInDrawer,
|
||||
...props
|
||||
}: ComponentProps<typeof NavigationMenu.List> & NavigationElementProps) => {
|
||||
const insideDrawer = useContext(NavigationDrawerContext);
|
||||
if (!insideDrawer && hide === true) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<NavigationMenu.List
|
||||
className={classNames(
|
||||
'flex gap-4 items-center',
|
||||
'[.navigation-content_&]:flex-col [.navigation-content_&]:items-start',
|
||||
'[.drawer-content_&]:flex-col [.drawer-content_&]:items-start [.drawer-content_&]:gap-6 [.drawer-content_&]:mt-2',
|
||||
'[.drawer-content_.navigation-content_&]:mt-6',
|
||||
determineIfHidden({ hide, hideInDrawer }),
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</NavigationMenu.List>
|
||||
);
|
||||
};
|
||||
export const NavigationTrigger = ({
|
||||
children,
|
||||
className,
|
||||
isActive = false,
|
||||
hide,
|
||||
hideInDrawer,
|
||||
...props
|
||||
}: ComponentProps<typeof NavigationMenu.Trigger> & {
|
||||
isActive?: boolean;
|
||||
} & NavigationElementProps) => {
|
||||
const { theme } = useContext(NavigationContext);
|
||||
const insideDrawer = useContext(NavigationDrawerContext);
|
||||
return (
|
||||
<NavigationMenu.Trigger
|
||||
className={classNames(
|
||||
'h-12 [.drawer-content_&]:h-min flex items-center relative gap-2',
|
||||
{
|
||||
'text-black dark:text-white': isActive && theme === 'system',
|
||||
'text-black': isActive && theme === 'light',
|
||||
'text-white': isActive && theme === 'dark',
|
||||
'text-black dark:[.drawer-content_&]:text-white':
|
||||
isActive && theme === 'yellow',
|
||||
},
|
||||
determineIfHidden({ hide, hideInDrawer }),
|
||||
className
|
||||
)}
|
||||
onPointerMove={(e) => e.preventDefault()} // disables hover
|
||||
onPointerLeave={(e) => e.preventDefault()} // disables hover
|
||||
disabled={insideDrawer}
|
||||
{...props}
|
||||
>
|
||||
<span>{children}</span>
|
||||
<span className="rotate-90 group-data-open/drawer:hidden">
|
||||
<Icon name="arrow-right" size={3} />
|
||||
</span>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={classNames(
|
||||
'absolute bottom-0 left-0 w-full h-[2px] [.navigation-content_&]:hidden [.drawer-content_&]:hidden',
|
||||
{ hidden: !isActive },
|
||||
{
|
||||
'bg-vega-yellow-550 dark:bg-vega-yellow-500': theme === 'system',
|
||||
'bg-vega-yellow-550': theme === 'light',
|
||||
'bg-vega-yellow-500': theme === 'dark',
|
||||
'bg-black': theme === 'yellow',
|
||||
}
|
||||
)}
|
||||
></div>
|
||||
</NavigationMenu.Trigger>
|
||||
);
|
||||
};
|
||||
export const NavigationContent = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof NavigationMenu.Content>) => {
|
||||
const { theme } = useContext(NavigationContext);
|
||||
const insideDrawer = useContext(NavigationDrawerContext);
|
||||
|
||||
const content = (
|
||||
<NavigationMenu.Content
|
||||
onPointerLeave={(e) => e.preventDefault()} // disables hover
|
||||
asChild
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'navigation-content',
|
||||
'absolute z-20 top-12 w-max',
|
||||
'p-2 mt-1',
|
||||
'text-vega-light-300 dark:text-vega-dark-300',
|
||||
|
||||
'border rounded border-vega-light-200 dark:border-vega-dark-200',
|
||||
'shadow-[8px_8px_16px_0_rgba(0,0,0,0.4)]',
|
||||
{
|
||||
'bg-white dark:bg-black': theme === 'system' || theme === 'yellow',
|
||||
'bg-white': theme === 'light',
|
||||
'bg-black': theme === 'dark',
|
||||
}
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</NavigationMenu.Content>
|
||||
);
|
||||
|
||||
const list = (
|
||||
<div className={classNames('navigation-content', 'border-none pl-4')}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
return insideDrawer ? list : content;
|
||||
};
|
||||
|
||||
export const NavigationLink = ({
|
||||
children,
|
||||
to,
|
||||
...props
|
||||
}: ComponentProps<typeof NavLink>) => {
|
||||
const { theme } = useContext(NavigationContext);
|
||||
const setDrawerOpen = useNavigationDrawer((state) => state.setDrawerOpen);
|
||||
return (
|
||||
<NavigationMenu.Link
|
||||
asChild
|
||||
onClick={() => {
|
||||
setDrawerOpen(false);
|
||||
}}
|
||||
>
|
||||
<NavLink
|
||||
to={to}
|
||||
className={classNames(
|
||||
'h-12 [.navigation-content_&]:h-min [.drawer-content_&]:h-min flex items-center relative'
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
<span
|
||||
className={classNames({
|
||||
'text-black dark:text-white': isActive && theme === 'system',
|
||||
'text-black': isActive && theme === 'light',
|
||||
'text-white': isActive && theme === 'dark',
|
||||
'text-black dark:[.navigation-content_&]:text-white dark:[.drawer-content_&]:text-white':
|
||||
isActive && theme === 'yellow',
|
||||
})}
|
||||
>
|
||||
{children as ReactNode}
|
||||
</span>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={classNames(
|
||||
'absolute bottom-0 left-0 w-full h-[2px] [.navigation-content_&]:hidden [.drawer-content_&]:hidden',
|
||||
{ hidden: !isActive },
|
||||
{
|
||||
'bg-vega-yellow-550 dark:bg-vega-yellow-500':
|
||||
theme === 'system',
|
||||
'bg-vega-yellow-550': theme === 'light',
|
||||
'bg-vega-yellow-500': theme === 'dark',
|
||||
'bg-black': theme === 'yellow',
|
||||
}
|
||||
)}
|
||||
></div>
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
</NavigationMenu.Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const Navigation = ({
|
||||
appName,
|
||||
homeLink = '/',
|
||||
children,
|
||||
actions,
|
||||
theme = 'system',
|
||||
breakpoints = [478, 1000],
|
||||
onResize,
|
||||
}: NavigationProps) => {
|
||||
const navigationRef = useRef<HTMLElement>(null);
|
||||
const actionsRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!navigationRef.current) return;
|
||||
const target = navigationRef.current;
|
||||
const currentWidth = Math.min(
|
||||
target.getBoundingClientRect().width,
|
||||
window.innerWidth
|
||||
);
|
||||
setSizeVariantClasses(breakpoints, currentWidth, target);
|
||||
onResize?.(currentWidth, target);
|
||||
|
||||
const handler = () => {
|
||||
const currentWidth = Math.min(
|
||||
target.getBoundingClientRect().width,
|
||||
window.innerWidth
|
||||
);
|
||||
setSizeVariantClasses(breakpoints, currentWidth, target);
|
||||
onResize?.(currentWidth, target);
|
||||
};
|
||||
window.addEventListener('resize', handler);
|
||||
return () => {
|
||||
window.removeEventListener('resize', handler);
|
||||
};
|
||||
}, [breakpoints, onResize]);
|
||||
|
||||
const { drawerOpen, setDrawerOpen } = useNavigationDrawer((state) => ({
|
||||
drawerOpen: state.drawerOpen,
|
||||
setDrawerOpen: state.setDrawerOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider value={{ theme }}>
|
||||
<NavigationMenu.Root
|
||||
ref={navigationRef}
|
||||
id="navigation"
|
||||
className={classNames(
|
||||
'h-12',
|
||||
'group flex gap-4 items-center',
|
||||
'border-b px-3 relative',
|
||||
// text
|
||||
{
|
||||
'text-black dark:text-white': theme === 'system',
|
||||
'text-black': theme === 'light' || theme === 'yellow',
|
||||
'text-white': theme === 'dark',
|
||||
},
|
||||
// border
|
||||
{
|
||||
'border-b-vega-light-200 dark:border-b-vega-dark-200':
|
||||
theme === 'system',
|
||||
'border-b-vega-light-200': theme === 'light',
|
||||
'border-b-vega-dark-200': theme === 'dark',
|
||||
'border-b-black': theme === 'yellow',
|
||||
},
|
||||
// background
|
||||
{
|
||||
'bg-white dark:bg-black': theme === 'system',
|
||||
'bg-white': theme === 'light',
|
||||
'bg-black': theme === 'dark',
|
||||
'bg-vega-yellow-500': theme === 'yellow',
|
||||
}
|
||||
)}
|
||||
data-testid="navigation"
|
||||
>
|
||||
<Logo appName={appName} theme={theme} homeLink={homeLink} />
|
||||
<div
|
||||
className={classNames(
|
||||
'navbar',
|
||||
'flex gap-4 h-12 items-center font-alpha text-lg calt',
|
||||
{
|
||||
'text-vega-light-300 dark:text-vega-dark-300': theme === 'system',
|
||||
'text-vega-light-300': theme === 'light',
|
||||
'text-vega-dark-300': theme === 'dark',
|
||||
'text-vega-dark-200': theme === 'yellow',
|
||||
}
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<Spacer />
|
||||
{(actions || children) && (
|
||||
<div ref={actionsRef} className="flex gap-2 items-center">
|
||||
{actions}
|
||||
<Drawer
|
||||
open={drawerOpen}
|
||||
onChange={(isOpen) => setDrawerOpen(isOpen)}
|
||||
trigger={<NavigationDrawerTrigger theme={theme} />}
|
||||
container={actionsRef.current}
|
||||
>
|
||||
<NavigationDrawerContent
|
||||
theme={theme}
|
||||
style={{
|
||||
paddingTop: `${
|
||||
navigationRef?.current?.getBoundingClientRect().bottom || 0
|
||||
}px`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</NavigationDrawerContent>
|
||||
</Drawer>
|
||||
</div>
|
||||
)}
|
||||
</NavigationMenu.Root>
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,5 @@
|
||||
type IconProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const SunIcon = ({ className }: IconProps) => (
|
||||
<svg viewBox="0 0 45 45" className={className || 'w-8 h-8'}>
|
||||
export const SunIcon = () => (
|
||||
<svg viewBox="0 0 45 45" className="w-8 h-8">
|
||||
<path
|
||||
d="M22.5 27.79a5.29 5.29 0 1 0 0-10.58 5.29 5.29 0 0 0 0 10.58Z"
|
||||
fill="currentColor"
|
||||
@@ -17,8 +13,8 @@ export const SunIcon = ({ className }: IconProps) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const MoonIcon = ({ className }: IconProps) => (
|
||||
<svg viewBox="0 0 45 45" className={className || 'w-8 h-8'}>
|
||||
export const MoonIcon = () => (
|
||||
<svg viewBox="0 0 45 45" className="w-8 h-8">
|
||||
<path
|
||||
d="M28.75 11.69A12.39 12.39 0 0 0 22.5 10a12.5 12.5 0 1 0 0 25c2.196 0 4.353-.583 6.25-1.69A12.46 12.46 0 0 0 35 22.5a12.46 12.46 0 0 0-6.25-10.81Zm-6.25 22a11.21 11.21 0 0 1-11.2-11.2 11.21 11.21 0 0 1 11.2-11.2c1.246 0 2.484.209 3.66.62a13.861 13.861 0 0 0-5 10.58 13.861 13.861 0 0 0 5 10.58 11.078 11.078 0 0 1-3.66.63v-.01Z"
|
||||
fill="currentColor"
|
||||
|
||||
@@ -77,7 +77,7 @@ const randomToast = (): Toast => {
|
||||
};
|
||||
|
||||
type PriceStore = { price: number; setPrice: (p: number) => void };
|
||||
const usePrice = create<PriceStore>()((set) => ({
|
||||
const usePrice = create<PriceStore>((set) => ({
|
||||
price: 0,
|
||||
setPrice: (p) => set((state) => ({ price: p })),
|
||||
}));
|
||||
|
||||
@@ -47,8 +47,8 @@ type Actions = {
|
||||
|
||||
type ToastsStore = State & Actions;
|
||||
|
||||
export const useToasts = create<ToastsStore>()(
|
||||
immer((set) => ({
|
||||
export const useToasts = create(
|
||||
immer<ToastsStore>((set, get) => ({
|
||||
toasts: {},
|
||||
count: 0,
|
||||
add: (toast) =>
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
type VegaLogoProps = {
|
||||
className?: string;
|
||||
};
|
||||
export const VegaLogo = ({ className }: VegaLogoProps) => {
|
||||
export const VegaLogo = () => {
|
||||
return (
|
||||
<svg
|
||||
aria-label={t('Vega logo')}
|
||||
className={className || 'h-6'}
|
||||
width="111"
|
||||
height="24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 111 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { act } from 'react-dom/test-utils';
|
||||
const zu = jest.requireActual('zustand'); // if using jest
|
||||
|
||||
// a variable to hold reset functions for all stores declared in the app
|
||||
const storeResetFns = new Set();
|
||||
|
||||
// when creating a store, we get its initial state, create a reset function and add it in the set
|
||||
export const create = (createState) => {
|
||||
const store = zu.create(createState);
|
||||
const initialState = store.getState();
|
||||
storeResetFns.add(() => store.setState(initialState, true));
|
||||
return store;
|
||||
};
|
||||
|
||||
// Reset all stores after each test run
|
||||
beforeEach(() => {
|
||||
act(() => storeResetFns.forEach((resetFn) => resetFn()));
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { StateCreator } from 'zustand';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
const { create: actualCreate } = jest.requireActual('zustand'); // if using jest
|
||||
|
||||
// a variable to hold reset functions for all stores declared in the app
|
||||
const storeResetFns = new Set<() => void>();
|
||||
|
||||
// when creating a store, we get its initial state, create a reset function and add it in the set
|
||||
export const create =
|
||||
() =>
|
||||
<S>(createState: StateCreator<S>) => {
|
||||
const store = actualCreate(createState);
|
||||
const initialState = store.getState();
|
||||
storeResetFns.add(() => store.setState(initialState, true));
|
||||
return store;
|
||||
};
|
||||
|
||||
// Reset all stores after each test run
|
||||
beforeEach(() => {
|
||||
act(() => storeResetFns.forEach((resetFn) => resetFn()));
|
||||
});
|
||||
@@ -8,11 +8,8 @@ import {
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletProvider } from '../provider';
|
||||
import {
|
||||
VegaConnectDialog,
|
||||
CLOSE_DELAY,
|
||||
useVegaWalletDialogStore,
|
||||
} from './connect-dialog';
|
||||
import { VegaConnectDialog, CLOSE_DELAY } from './connect-dialog';
|
||||
import type { VegaWalletDialogStore } from './connect-dialog';
|
||||
import type { VegaConnectDialogProps } from '..';
|
||||
import {
|
||||
ClientErrors,
|
||||
@@ -27,6 +24,11 @@ import { ChainIdDocument } from '@vegaprotocol/react-helpers';
|
||||
|
||||
const mockUpdateDialogOpen = jest.fn();
|
||||
const mockCloseVegaDialog = jest.fn();
|
||||
const mockStoreObj: Partial<VegaWalletDialogStore> = {
|
||||
updateVegaWalletDialog: mockUpdateDialogOpen,
|
||||
closeVegaWalletDialog: mockCloseVegaDialog,
|
||||
vegaWalletDialogOpen: true,
|
||||
};
|
||||
|
||||
jest.mock('@vegaprotocol/environment');
|
||||
|
||||
@@ -42,6 +44,11 @@ useEnvironment.mockImplementation(() => ({
|
||||
HOSTED_WALLET_URL: mockHostedWalletUrl,
|
||||
}));
|
||||
|
||||
jest.mock('zustand', () => ({
|
||||
create: () => (storeGetter: (store: VegaWalletDialogStore) => unknown) =>
|
||||
storeGetter(mockStoreObj as VegaWalletDialogStore),
|
||||
}));
|
||||
|
||||
let defaultProps: VegaConnectDialogProps;
|
||||
|
||||
const INITIAL_KEY = 'some-key';
|
||||
@@ -59,12 +66,6 @@ beforeEach(() => {
|
||||
defaultProps = {
|
||||
connectors,
|
||||
};
|
||||
|
||||
useVegaWalletDialogStore.setState({
|
||||
updateVegaWalletDialog: mockUpdateDialogOpen,
|
||||
closeVegaWalletDialog: mockCloseVegaDialog,
|
||||
vegaWalletDialogOpen: true,
|
||||
});
|
||||
});
|
||||
|
||||
const mockVegaWalletUrl = 'http://mock.wallet.com';
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface VegaConnectDialogProps {
|
||||
onChangeOpen?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const useVegaWalletDialogStore = create<VegaWalletDialogStore>()(
|
||||
export const useVegaWalletDialogStore = create<VegaWalletDialogStore>(
|
||||
(set) => ({
|
||||
vegaWalletDialogOpen: false,
|
||||
updateVegaWalletDialog: (open: boolean) =>
|
||||
|
||||
@@ -1,25 +1,5 @@
|
||||
import { WalletClientError } from '@vegaprotocol/wallet-client';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import type { PeggedReference } from '@vegaprotocol/types';
|
||||
|
||||
export interface LiquidityProvisionSubmission {
|
||||
liquidityProvisionSubmission: LiquidityProvisionBody;
|
||||
pubKey: string;
|
||||
propagate: boolean;
|
||||
}
|
||||
export interface LiquidityProvisionBody {
|
||||
marketId: string;
|
||||
commitmentAmount: string;
|
||||
fee: string;
|
||||
buys?: PeggedOrders[];
|
||||
sells?: PeggedOrders[];
|
||||
}
|
||||
|
||||
export interface PeggedOrders {
|
||||
offset: string;
|
||||
proportion: string;
|
||||
reference: PeggedReference;
|
||||
}
|
||||
|
||||
export interface DelegateSubmissionBody {
|
||||
delegateSubmission: {
|
||||
@@ -356,8 +336,7 @@ export type Transaction =
|
||||
| OrderAmendmentBody
|
||||
| ProposalSubmissionBody
|
||||
| BatchMarketInstructionSubmissionBody
|
||||
| TransferBody
|
||||
| LiquidityProvisionSubmission;
|
||||
| TransferBody;
|
||||
|
||||
export const isWithdrawTransaction = (
|
||||
transaction: Transaction
|
||||
|
||||
@@ -52,8 +52,8 @@ export interface VegaTransactionStore {
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const useVegaTransactionStore = create<VegaTransactionStore>()(
|
||||
subscribeWithSelector((set, get) => ({
|
||||
export const useVegaTransactionStore = create(
|
||||
subscribeWithSelector<VegaTransactionStore>((set, get) => ({
|
||||
transactions: [] as VegaStoredTxState[],
|
||||
create: (body: Transaction, order?: OrderTxUpdateFieldsFragment) => {
|
||||
const transactions = get().transactions;
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx",
|
||||
"jest.config.ts",
|
||||
"__mocks__"
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user