fix(trading): remove additional page for closed markets (#5042)

This commit is contained in:
Matthew Russell 2023-10-16 11:18:26 -07:00 committed by GitHub
parent f416fa4a25
commit 72ccf96523
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 339 additions and 314 deletions

View File

@ -1,5 +0,0 @@
import MarketPage from '../market';
export const ClosedMarketPage = () => {
return <MarketPage closed />;
};

View File

@ -1 +0,0 @@
export { ClosedMarketPage as default } from './closed-market';

View File

@ -0,0 +1,48 @@
import { t } from '@vegaprotocol/i18n';
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
import {
SidebarButton,
SidebarDivider,
ViewType,
} from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const LiquiditySidebar = () => {
const currentRouteId = useGetCurrentRouteId();
return (
<>
<SidebarButton
view={ViewType.Deposit}
icon={VegaIconNames.DEPOSIT}
tooltip={t('Deposit')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Withdraw}
icon={VegaIconNames.WITHDRAW}
tooltip={t('Withdraw')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Transfer}
icon={VegaIconNames.TRANSFER}
tooltip={t('Transfer')}
routeId={currentRouteId}
/>
<SidebarDivider />
<SidebarButton
view={ViewType.Order}
icon={VegaIconNames.TICKET}
tooltip={t('Order')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Info}
icon={VegaIconNames.BREAKDOWN}
tooltip={t('Market specification')}
routeId={currentRouteId}
/>
</>
);
};

View File

@ -9,10 +9,9 @@ import { useGlobalStore, usePageTitleStore } from '../../stores';
import { TradeGrid } from './trade-grid'; import { TradeGrid } from './trade-grid';
import { TradePanels } from './trade-panels'; import { TradePanels } from './trade-panels';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { Links, Routes } from '../../lib/links'; import { Links } from '../../lib/links';
import { ViewType, useSidebar } from '../../components/sidebar'; import { ViewType, useSidebar } from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { MarketState } from '@vegaprotocol/types';
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => { const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
return markPrice && decimalPlaces return markPrice && decimalPlaces
@ -57,7 +56,7 @@ const TitleUpdater = ({
return null; return null;
}; };
export const MarketPage = ({ closed }: { closed?: boolean }) => { export const MarketPage = () => {
const { marketId } = useParams(); const { marketId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const currentRouteId = useGetCurrentRouteId(); const currentRouteId = useGetCurrentRouteId();
@ -70,25 +69,11 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
const { data, loading } = useMarket(marketId); const { data, loading } = useMarket(marketId);
useEffect(() => {
if (
data?.state &&
[
MarketState.STATE_SETTLED,
MarketState.STATE_TRADING_TERMINATED,
].includes(data.state) &&
currentRouteId !== Routes.CLOSED_MARKETS &&
marketId
) {
navigate(Links.CLOSED_MARKETS(marketId));
}
}, [data?.state, currentRouteId, navigate, marketId]);
useEffect(() => { useEffect(() => {
if (data?.id && data.id !== lastMarketId && !closed) { if (data?.id && data.id !== lastMarketId && !closed) {
update({ marketId: data.id }); update({ marketId: data.id });
} }
}, [update, lastMarketId, data?.id, closed]); }, [update, lastMarketId, data?.id]);
useEffect(() => { useEffect(() => {
if (largeScreen && view === undefined) { if (largeScreen && view === undefined) {
@ -97,7 +82,7 @@ export const MarketPage = ({ closed }: { closed?: boolean }) => {
currentRouteId currentRouteId
); );
} }
}, [setViews, view, currentRouteId, largeScreen, closed]); }, [setViews, view, currentRouteId, largeScreen]);
const pinnedAsset = data && getAsset(data); const pinnedAsset = data && getAsset(data);

View File

@ -363,12 +363,12 @@ describe('Closed', () => {
const container = within( const container = within(
document.querySelector('.ag-center-cols-container') as HTMLElement document.querySelector('.ag-center-cols-container') as HTMLElement
); );
const cell = container.getAllByRole('gridcell', {
name: (_name, element) => element.getAttribute('col-id') === 'code',
})[0];
expect(within(cell).getByTestId('stack-cell-secondary')).toHaveTextContent( const cells = await container.findAllByRole('gridcell');
'PRNT' const cell = cells.find((el) => el.getAttribute('col-id') === 'code');
);
expect(
within(cell as HTMLElement).getByTestId('stack-cell-secondary')
).toHaveTextContent('PRNT');
}); });
}); });

View File

@ -1,3 +1,4 @@
import type { CellClickedEvent } from 'ag-grid-community';
import compact from 'lodash/compact'; import compact from 'lodash/compact';
import { isAfter } from 'date-fns'; import { isAfter } from 'date-fns';
import type { import type {
@ -5,6 +6,7 @@ import type {
VegaValueFormatterParams, VegaValueFormatterParams,
} from '@vegaprotocol/datagrid'; } from '@vegaprotocol/datagrid';
import { AgGrid, COL_DEFS } from '@vegaprotocol/datagrid'; import { AgGrid, COL_DEFS } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import type { Asset } from '@vegaprotocol/types'; import type { Asset } from '@vegaprotocol/types';
@ -17,13 +19,11 @@ import {
import { closedMarketsWithDataProvider, getAsset } from '@vegaprotocol/markets'; import { closedMarketsWithDataProvider, getAsset } from '@vegaprotocol/markets';
import type { DataSourceFilterFragment } from '@vegaprotocol/markets'; import type { DataSourceFilterFragment } from '@vegaprotocol/markets';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { SettlementDateCell } from './settlement-date-cell'; import { SettlementDateCell } from './settlement-date-cell';
import { SettlementPriceCell } from './settlement-price-cell'; import { SettlementPriceCell } from './settlement-price-cell';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { MarketCodeCell } from './market-code-cell'; import { MarketCodeCell } from './market-code-cell';
import { MarketActionsDropdown } from './market-table-actions'; import { MarketActionsDropdown } from './market-table-actions';
import type { CellClickedEvent } from 'ag-grid-community';
import { useClosedMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
type SettlementAsset = Pick< type SettlementAsset = Pick<
Asset, Asset,
@ -127,7 +127,7 @@ const ClosedMarketsDataGrid = ({
rowData: Row[]; rowData: Row[];
error: Error | undefined; error: Error | undefined;
}) => { }) => {
const handleOnSelect = useClosedMarketClickHandler(); const handleOnSelect = useMarketClickHandler();
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open); const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
const colDefs = useMemo(() => { const colDefs = useMemo(() => {
@ -302,8 +302,11 @@ const ClosedMarketsDataGrid = ({
return; return;
} }
// @ts-ignore metaKey exists handleOnSelect(
handleOnSelect(data.id, event ? event.metaKey : false); data.id,
// @ts-ignore metaKey exists
event ? event.metaKey : false
);
}} }}
/> />
); );

View File

@ -0,0 +1,67 @@
import { Route, Routes, useParams } from 'react-router-dom';
import { MarketState } from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
import { useMarket } from '@vegaprotocol/markets';
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
import {
SidebarButton,
SidebarDivider,
ViewType,
} from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const MarketsSidebar = () => {
const { marketId } = useParams();
const currentRouteId = useGetCurrentRouteId();
const { data } = useMarket(marketId);
const active =
data &&
[MarketState.STATE_ACTIVE, MarketState.STATE_PENDING].includes(data.state);
return (
<>
<SidebarButton
view={ViewType.Deposit}
icon={VegaIconNames.DEPOSIT}
tooltip={t('Deposit')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Withdraw}
icon={VegaIconNames.WITHDRAW}
tooltip={t('Withdraw')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Transfer}
icon={VegaIconNames.TRANSFER}
tooltip={t('Transfer')}
routeId={currentRouteId}
/>
<Routes>
<Route
path=":marketId"
element={
<>
<SidebarDivider />
{active && (
<SidebarButton
view={ViewType.Order}
icon={VegaIconNames.TICKET}
tooltip={t('Order')}
routeId={currentRouteId}
/>
)}
<SidebarButton
view={ViewType.Info}
icon={VegaIconNames.BREAKDOWN}
tooltip={t('Market specification')}
routeId={currentRouteId}
/>
</>
}
/>
</Routes>
</>
);
};

View File

@ -0,0 +1,31 @@
import { t } from '@vegaprotocol/i18n';
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { SidebarButton, ViewType } from '../../components/sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const PortfolioSidebar = () => {
const currentRouteId = useGetCurrentRouteId();
return (
<>
<SidebarButton
view={ViewType.Deposit}
icon={VegaIconNames.DEPOSIT}
tooltip={t('Deposit')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Withdraw}
icon={VegaIconNames.WITHDRAW}
tooltip={t('Withdraw')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Transfer}
icon={VegaIconNames.TRANSFER}
tooltip={t('Transfer')}
routeId={currentRouteId}
/>
</>
);
};

View File

@ -1,11 +1,16 @@
import { Outlet, Routes, Route } from 'react-router-dom'; import type { ReactNode } from 'react';
import { Outlet } from 'react-router-dom';
import { Sidebar, SidebarContent, useSidebar } from '../sidebar'; import { Sidebar, SidebarContent, useSidebar } from '../sidebar';
import classNames from 'classnames'; import classNames from 'classnames';
import { MarketHeader } from '../market-header';
import { LiquidityHeader } from '../liquidity-header';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
export const LayoutWithSidebar = () => { export const LayoutWithSidebar = ({
header,
sidebar,
}: {
header?: ReactNode;
sidebar?: ReactNode;
}) => {
const currentRouteId = useGetCurrentRouteId(); const currentRouteId = useGetCurrentRouteId();
const views = useSidebar((store) => store.views); const views = useSidebar((store) => store.views);
const sidebarView = views[currentRouteId] || null; const sidebarView = views[currentRouteId] || null;
@ -20,16 +25,7 @@ export const LayoutWithSidebar = () => {
return ( return (
<div className={gridClasses}> <div className={gridClasses}>
<div className="col-span-full"> <div className="col-span-full">{header}</div>
<Routes>
<Route path="markets/:marketId" element={<MarketHeader />} />
<Route
path="markets/all/closed/:marketId"
element={<MarketHeader />}
/>
<Route path="liquidity/:marketId" element={<LiquidityHeader />} />
</Routes>
</div>
<main <main
className={classNames('col-start-1 col-end-1', { className={classNames('col-start-1 col-end-1', {
'lg:col-end-3': !sidebarOpen, 'lg:col-end-3': !sidebarOpen,
@ -57,7 +53,7 @@ export const LayoutWithSidebar = () => {
'lg:row-start-2 lg:row-span-full lg:col-start-3' 'lg:row-start-2 lg:row-span-full lg:col-start-3'
)} )}
> >
<Sidebar /> <Sidebar options={sidebar} />
</div> </div>
</div> </div>
); );

View File

@ -1,6 +1,6 @@
import { Popover, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit'; import { Popover, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { Header, HeaderTitle } from '../header'; import { Header, HeaderTitle } from '../header';
import { useParams } from 'react-router-dom'; import { Route, Routes, useParams } from 'react-router-dom';
import { MarketSelector } from '../../components/market-selector/market-selector'; import { MarketSelector } from '../../components/market-selector/market-selector';
import { MarketHeaderStats } from '../../client-pages/market/market-header-stats'; import { MarketHeaderStats } from '../../client-pages/market/market-header-stats';
import { useMarket, useMarketList } from '@vegaprotocol/markets'; import { useMarket, useMarketList } from '@vegaprotocol/markets';
@ -19,35 +19,44 @@ export const MarketHeader = () => {
if (!data) return null; if (!data) return null;
return ( return (
<Header <Routes>
title={ <Route
<Popover path=":marketId"
open={open} element={
onChange={setOpen} <Header
trigger={ title={
<HeaderTitle> <Popover
<span> open={open}
{data.tradableInstrument.instrument.code} onChange={setOpen}
<span className="ml-1 text-xs uppercase text-muted"> trigger={
{data.tradableInstrument.instrument.product.__typename && <HeaderTitle>
ProductTypeShortName[ <span>
data.tradableInstrument.instrument.product.__typename {data.tradableInstrument.instrument.code}
]} <span className="ml-1 text-xs uppercase text-muted">
</span> {data.tradableInstrument.instrument.product
</span> .__typename &&
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} /> ProductTypeShortName[
</HeaderTitle> data.tradableInstrument.instrument.product
} .__typename
alignOffset={-10} ]}
> </span>
<MarketSelector </span>
currentMarketId={marketId} <VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />
onSelect={() => setOpen(false)} </HeaderTitle>
/> }
</Popover> alignOffset={-10}
} >
> <MarketSelector
<MarketHeaderStats market={data} /> currentMarketId={marketId}
</Header> onSelect={() => setOpen(false)}
/>
</Popover>
}
>
<MarketHeaderStats market={data} />
</Header>
}
/>
</Routes>
); );
}; };

View File

@ -63,7 +63,7 @@ describe('Navbar', () => {
const expectedLinks = [ const expectedLinks = [
['/', ''], ['/', ''],
['/markets/all', 'Markets'], ['/markets', 'Markets'],
[`/markets/${marketId}`, 'Trading'], [`/markets/${marketId}`, 'Trading'],
['/portfolio', 'Portfolio'], ['/portfolio', 'Portfolio'],
['/referrals', 'Referrals'], ['/referrals', 'Referrals'],
@ -80,7 +80,7 @@ describe('Navbar', () => {
}); });
it('Markets page route should not match empty market page', () => { it('Markets page route should not match empty market page', () => {
renderComponent(['/markets/all']); renderComponent(['/markets']);
expect(screen.getByRole('link', { name: 'Markets' })).toHaveClass('active'); expect(screen.getByRole('link', { name: 'Markets' })).toHaveClass('active');
expect(screen.getByRole('link', { name: 'Trading' })).not.toHaveClass( expect(screen.getByRole('link', { name: 'Trading' })).not.toHaveClass(
'active' 'active'
@ -96,7 +96,7 @@ describe('Navbar', () => {
const menu = within(menuEl); const menu = within(menuEl);
const expectedLinks = [ const expectedLinks = [
['/markets/all', 'Markets'], ['/markets', 'Markets'],
[`/markets/${marketId}`, 'Trading'], [`/markets/${marketId}`, 'Trading'],
['/portfolio', 'Portfolio'], ['/portfolio', 'Portfolio'],
['/referrals', 'Referrals'], ['/referrals', 'Referrals'],

View File

@ -264,6 +264,7 @@ const NavbarLink = ({
<N.Link asChild={true}> <N.Link asChild={true}>
<NavLink <NavLink
to={to} to={to}
end={true}
className={classNames( className={classNames(
'block lg:flex lg:h-full flex-col justify-center', 'block lg:flex lg:h-full flex-col justify-center',
'px-6 py-2 lg:p-0 text-lg lg:text-sm', 'px-6 py-2 lg:p-0 text-lg lg:text-sm',

View File

@ -3,14 +3,12 @@ import { Filter, OrderListManager } from '@vegaprotocol/orders';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit'; import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import { import { useNavigateWithMeta } from '../../lib/hooks/use-market-click-handler';
useMarketClickHandler,
useMarketLiquidityClickHandler,
} from '../../lib/hooks/use-market-click-handler';
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import type { DataGridStore } from '../../stores/datagrid-store-slice'; import type { DataGridStore } from '../../stores/datagrid-store-slice';
import { OrderStatus } from '@vegaprotocol/types'; import { OrderStatus } from '@vegaprotocol/types';
import { Links } from '../../lib/links';
export const FilterStatusValue = { export const FilterStatusValue = {
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED], [Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
@ -32,8 +30,7 @@ const AUTO_SIZE_COLUMNS = ['instrument-code'];
export const OrdersContainer = ({ filter }: OrderContainerProps) => { export const OrdersContainer = ({ filter }: OrderContainerProps) => {
const { pubKey, isReadOnly } = useVegaWallet(); const { pubKey, isReadOnly } = useVegaWallet();
const onMarketClick = useMarketClickHandler(true); const navigate = useNavigateWithMeta();
const onOrderTypeClick = useMarketLiquidityClickHandler();
const { gridState, updateGridState } = useOrderListGridState(filter); const { gridState, updateGridState } = useOrderListGridState(filter);
const gridStoreCallbacks = useDataGridEvents( const gridStoreCallbacks = useDataGridEvents(
gridState, gridState,
@ -51,8 +48,12 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
<OrderListManager <OrderListManager
partyId={pubKey} partyId={pubKey}
filter={filter} filter={filter}
onMarketClick={onMarketClick} onMarketClick={(marketId, metaKey) => {
onOrderTypeClick={onOrderTypeClick} navigate(Links.MARKET(marketId), metaKey);
}}
onOrderTypeClick={(marketId, metaKey) =>
navigate(Links.LIQUIDITY(marketId), metaKey)
}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
gridProps={gridStoreCallbacks} gridProps={gridStoreCallbacks}
/> />

View File

@ -38,79 +38,68 @@ const walletContext = {
} as VegaWalletContextShape; } as VegaWalletContextShape;
describe('Sidebar', () => { describe('Sidebar', () => {
it.each(['/markets/all', '/portfolio'])( beforeEach(() => {
'does not render ticket and info', useSidebar.setState({ views: {} });
(path) => { });
render(
<VegaWalletContext.Provider value={walletContext}>
<MemoryRouter initialEntries={[path]}>
<Sidebar />
</MemoryRouter>
</VegaWalletContext.Provider>
);
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument(); it('renders options prop', () => {
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Withdraw)).toBeInTheDocument();
expect(screen.getByTestId('node-health')).toBeInTheDocument();
// no order or info sidebars
expect(screen.queryByTestId(ViewType.Order)).not.toBeInTheDocument();
expect(screen.queryByTestId(ViewType.Info)).not.toBeInTheDocument();
}
);
it('renders ticket and info on market pages', () => {
render( render(
<VegaWalletContext.Provider value={walletContext}> <VegaWalletContext.Provider value={walletContext}>
<MemoryRouter initialEntries={['/markets/ABC']}> <MemoryRouter>
<Sidebar /> <Sidebar options={<div data-testid="options" />} />
</MemoryRouter> </MemoryRouter>
</VegaWalletContext.Provider> </VegaWalletContext.Provider>
); );
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument(); expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument(); expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Withdraw)).toBeInTheDocument();
expect(screen.getByTestId('node-health')).toBeInTheDocument(); expect(screen.getByTestId('node-health')).toBeInTheDocument();
expect(screen.getByTestId('options')).toBeInTheDocument();
// order and info sidebars are shown
expect(screen.getByTestId(ViewType.Order)).toBeInTheDocument();
expect(screen.getByTestId(ViewType.Info)).toBeInTheDocument();
}); });
it('renders selected state', async () => { it('renders selected state', async () => {
const routeId = '/markets/ABC';
render( render(
<VegaWalletContext.Provider value={walletContext}> <VegaWalletContext.Provider value={walletContext}>
<MemoryRouter initialEntries={['/markets/ABC']}> <MemoryRouter initialEntries={[routeId]}>
<Sidebar /> <Sidebar
options={
<SidebarButton
view={ViewType.Deposit}
icon={VegaIconNames.DEPOSIT}
tooltip="Deposit"
routeId={'/markets/:marketId'}
/>
}
/>
</MemoryRouter> </MemoryRouter>
</VegaWalletContext.Provider> </VegaWalletContext.Provider>
); );
const settingsButton = screen.getByTestId(ViewType.Settings); const settingsButton = screen.getByTestId(ViewType.Settings);
const orderButton = screen.getByTestId(ViewType.Order); const depositButton = screen.getByTestId(ViewType.Deposit);
// select settings first // select settings first
await userEvent.click(settingsButton); await userEvent.click(settingsButton);
expect(settingsButton).toHaveClass('bg-vega-yellow text-black'); expect(settingsButton).toHaveClass('bg-vega-yellow text-black');
// switch to order // switch to deposit
await userEvent.click(orderButton); await userEvent.click(depositButton);
expect(settingsButton).not.toHaveClass('bg-vega-yellow text-black'); expect(settingsButton).not.toHaveClass('bg-vega-yellow text-black');
expect(orderButton).toHaveClass('bg-vega-yellow text-black'); expect(depositButton).toHaveClass('bg-vega-yellow text-black');
// close order // close order
await userEvent.click(orderButton); await userEvent.click(depositButton);
expect(orderButton).not.toHaveClass('bg-vega-yellow text-black'); expect(depositButton).not.toHaveClass('bg-vega-yellow text-black');
}); });
}); });
describe('SidebarContent', () => { describe('SidebarContent', () => {
beforeEach(() => {
useSidebar.setState({ views: {} });
});
it('renders the correct content', () => { it('renders the correct content', () => {
const { container } = render( const { container } = render(
<VegaWalletContext.Provider value={walletContext}> <VegaWalletContext.Provider value={walletContext}>

View File

@ -1,7 +1,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Route, Routes, useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { create } from 'zustand'; import { create } from 'zustand';
import { TransferContainer } from '@vegaprotocol/accounts'; import { TransferContainer } from '@vegaprotocol/accounts';
import { DealTicketContainer } from '@vegaprotocol/deal-ticket'; import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
@ -50,108 +50,14 @@ type SidebarView =
type: ViewType.Settings; type: ViewType.Settings;
}; };
const MarketSidebarButtons = () => { export const Sidebar = ({ options }: { options?: ReactNode }) => {
const currentRouteId = useGetCurrentRouteId();
return (
<>
<SidebarButton
view={ViewType.Order}
icon={VegaIconNames.TICKET}
tooltip={t('Order')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Info}
icon={VegaIconNames.BREAKDOWN}
tooltip={t('Market specification')}
routeId={currentRouteId}
/>
</>
);
};
const AssetSidebarButtons = () => {
const currentRouteId = useGetCurrentRouteId();
return (
<>
<SidebarButton
view={ViewType.Deposit}
icon={VegaIconNames.DEPOSIT}
tooltip={t('Deposit')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Withdraw}
icon={VegaIconNames.WITHDRAW}
tooltip={t('Withdraw')}
routeId={currentRouteId}
/>
<SidebarButton
view={ViewType.Transfer}
icon={VegaIconNames.TRANSFER}
tooltip={t('Transfer')}
routeId={currentRouteId}
/>
</>
);
};
export const Sidebar = () => {
const currentRouteId = useGetCurrentRouteId(); const currentRouteId = useGetCurrentRouteId();
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1'; const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen); const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
const { pubKeys } = useVegaWallet(); const { pubKeys } = useVegaWallet();
return ( return (
<div className="flex h-full p-1 lg:flex-col gap-2" data-testid="sidebar"> <div className="flex h-full p-1 lg:flex-col gap-2" data-testid="sidebar">
<nav className={navClasses}> {options && <nav className={navClasses}>{options}</nav>}
<Routes>
<Route path="markets/all" element={<AssetSidebarButtons />} />
<Route path="portfolio">
<Route
// Show deposit/withdraw/transfer sidebar options only on portflio dashboard (index)
index={true}
element={<AssetSidebarButtons />}
/>
</Route>
<Route
path="markets/:marketId"
element={
<>
<AssetSidebarButtons />
<SidebarDivider />
<MarketSidebarButtons />
</>
}
/>
<Route
path="liquidity/:marketId"
element={
<>
<AssetSidebarButtons />
<SidebarDivider />
<MarketSidebarButtons />
</>
}
/>
<Route
path="markets/all/closed/:marketId"
element={
<>
<AssetSidebarButtons />
<SidebarDivider />
<SidebarButton
view={ViewType.Info}
icon={VegaIconNames.BREAKDOWN}
tooltip={t('Market specification')}
routeId={currentRouteId}
/>
</>
}
/>
</Routes>
</nav>
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}> <nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
<SidebarButton <SidebarButton
view={ViewType.ViewAs} view={ViewType.ViewAs}
@ -234,7 +140,7 @@ export const SidebarButton = ({
); );
}; };
const SidebarDivider = () => { export const SidebarDivider = () => {
return ( return (
<div <div
className="w-px h-4 bg-vega-clight-600 dark:bg-vega-cdark-600 lg:w-4 lg:h-px" className="w-px h-4 bg-vega-clight-600 dark:bg-vega-cdark-600 lg:w-4 lg:h-px"

View File

@ -1,5 +1,4 @@
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useCallback } from 'react';
import { Links } from '../../lib/links'; import { Links } from '../../lib/links';
export const useMarketClickHandler = (replace = false) => { export const useMarketClickHandler = (replace = false) => {
@ -15,17 +14,10 @@ export const useMarketClickHandler = (replace = false) => {
}; };
}; };
export const useMarketLiquidityClickHandler = () => { export const useNavigateWithMeta = (replace = false) => {
return useCallback((selectedId: string, metaKey?: boolean) => {
window.open(`/#/liquidity/${selectedId}`, metaKey ? '_blank' : '_self');
}, []);
};
export const useClosedMarketClickHandler = (replace = false) => {
const navigate = useNavigate(); const navigate = useNavigate();
return (selectedId: string, metaKey?: boolean) => { return (link: string, metaKey?: boolean) => {
const link = Links.CLOSED_MARKETS(selectedId);
if (metaKey) { if (metaKey) {
window.open(`/#${link}`, '_blank'); window.open(`/#${link}`, '_blank');
} else { } else {

View File

@ -4,8 +4,7 @@ import trimEnd from 'lodash/trimEnd';
// href creation // href creation
export const Routes = { export const Routes = {
HOME: '/', HOME: '/',
MARKETS: '/markets/all', MARKETS: '/markets',
CLOSED_MARKETS: '/markets/all/closed/:marketId',
MARKET: '/markets/:marketId', MARKET: '/markets/:marketId',
LIQUIDITY: '/liquidity/:marketId', LIQUIDITY: '/liquidity/:marketId',
PORTFOLIO: '/portfolio', PORTFOLIO: '/portfolio',
@ -29,8 +28,6 @@ export const Links: ConsoleLinks = {
MARKET: (marketId: string) => MARKET: (marketId: string) =>
trimEnd(Routes.MARKET.replace(':marketId', marketId)), trimEnd(Routes.MARKET.replace(':marketId', marketId)),
MARKETS: () => Routes.MARKETS, MARKETS: () => Routes.MARKETS,
CLOSED_MARKETS: (marketId: string) =>
trimEnd(Routes.CLOSED_MARKETS.replace(':marketId', marketId)),
PORTFOLIO: () => Routes.PORTFOLIO, PORTFOLIO: () => Routes.PORTFOLIO,
LIQUIDITY: (marketId: string) => LIQUIDITY: (marketId: string) =>
trimEnd(Routes.LIQUIDITY.replace(':marketId', marketId)), trimEnd(Routes.LIQUIDITY.replace(':marketId', marketId)),

View File

@ -1,5 +1,5 @@
import type { RouteObject } from 'react-router-dom'; import type { RouteObject } from 'react-router-dom';
import { Navigate, Outlet, useRoutes } from 'react-router-dom'; import { Navigate, useRoutes } from 'react-router-dom';
import { lazy, Suspense } from 'react'; import { lazy, Suspense } from 'react';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { Loader, Splash } from '@vegaprotocol/ui-toolkit'; import { Loader, Splash } from '@vegaprotocol/ui-toolkit';
@ -13,7 +13,7 @@ import { Assets } from '../client-pages/assets';
import { Deposit } from '../client-pages/deposit'; import { Deposit } from '../client-pages/deposit';
import { Withdraw } from '../client-pages/withdraw'; import { Withdraw } from '../client-pages/withdraw';
import { Transfer } from '../client-pages/transfer'; import { Transfer } from '../client-pages/transfer';
import { Routes } from '../lib/links'; import { Routes as AppRoutes } from '../lib/links';
import { LayoutWithSky } from '../client-pages/referrals/layout'; import { LayoutWithSky } from '../client-pages/referrals/layout';
import { Referrals } from '../client-pages/referrals/referrals'; import { Referrals } from '../client-pages/referrals/referrals';
import { ReferralStatistics } from '../client-pages/referrals/referral-statistics'; import { ReferralStatistics } from '../client-pages/referrals/referral-statistics';
@ -22,11 +22,15 @@ import { CreateCodeContainer } from '../client-pages/referrals/create-code-form'
import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-boundary'; import { NotFound as ReferralNotFound } from '../client-pages/referrals/error-boundary';
import { compact } from 'lodash'; import { compact } from 'lodash';
import { FLAGS } from '@vegaprotocol/environment'; import { FLAGS } from '@vegaprotocol/environment';
import { LiquidityHeader } from '../components/liquidity-header';
import { MarketHeader } from '../components/market-header';
import { PortfolioSidebar } from '../client-pages/portfolio/portfolio-sidebar';
import { LiquiditySidebar } from '../client-pages/liquidity/liquidity-sidebar';
import { MarketsSidebar } from '../client-pages/markets/markets-sidebar';
// These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM // These must remain dynamically imported as pennant cannot be compiled by nextjs due to ESM
// Using dynamic imports is a workaround for this until pennant is published as ESM // Using dynamic imports is a workaround for this until pennant is published as ESM
const MarketPage = lazy(() => import('../client-pages/market')); const MarketPage = lazy(() => import('../client-pages/market'));
const ClosedMarketPage = lazy(() => import('../client-pages/closed-market'));
const Portfolio = lazy(() => import('../client-pages/portfolio')); const Portfolio = lazy(() => import('../client-pages/portfolio'));
const NotFound = () => ( const NotFound = () => (
@ -36,18 +40,21 @@ const NotFound = () => (
); );
export const routerConfig: RouteObject[] = compact([ export const routerConfig: RouteObject[] = compact([
// Pages that don't use the LayoutWithSidebar must come first {
// to ensure they are matched before the catch all route '/*' index: true,
element: <Home />,
id: AppRoutes.HOME,
},
{ {
path: 'disclaimer', path: 'disclaimer',
element: <LayoutCentered />, element: <LayoutCentered />,
id: Routes.DISCLAIMER, id: AppRoutes.DISCLAIMER,
children: [{ index: true, element: <Disclaimer /> }], children: [{ index: true, element: <Disclaimer /> }],
}, },
// Referrals routing (the pages should be available if the feature flag is on) // Referrals routing (the pages should be available if the feature flag is on)
FLAGS.REFERRALS FLAGS.REFERRALS
? { ? {
path: Routes.REFERRALS, path: AppRoutes.REFERRALS,
element: <LayoutWithSky />, element: <LayoutWithSky />,
children: [ children: [
{ {
@ -58,11 +65,11 @@ export const routerConfig: RouteObject[] = compact([
element: <ReferralStatistics />, element: <ReferralStatistics />,
}, },
{ {
path: Routes.REFERRALS_CREATE_CODE, path: AppRoutes.REFERRALS_CREATE_CODE,
element: <CreateCodeContainer />, element: <CreateCodeContainer />,
}, },
{ {
path: Routes.REFERRALS_APPLY_CODE, path: AppRoutes.REFERRALS_APPLY_CODE,
element: <ApplyCodeForm />, element: <ApplyCodeForm />,
}, },
], ],
@ -74,75 +81,74 @@ export const routerConfig: RouteObject[] = compact([
], ],
} }
: undefined, : undefined,
// All other pages will use the sidebar
{ {
path: '/*', path: 'markets/*',
element: <LayoutWithSidebar />, element: (
<LayoutWithSidebar
header={<MarketHeader />}
sidebar={<MarketsSidebar />}
/>
),
children: [ children: [
{ {
index: true, index: true,
element: <Home />, element: <MarketsPage />,
id: Routes.HOME, id: AppRoutes.MARKETS,
}, },
{ {
path: 'markets', path: 'all',
element: <Outlet />, element: <Navigate to="/markets" />,
children: [
{
index: true,
element: <Navigate to="all" />,
},
{
path: 'all',
element: <MarketsPage />,
id: Routes.MARKETS,
},
{
path: ':marketId',
element: <MarketPage />,
id: Routes.MARKET,
},
{
path: 'all/closed/:marketId',
element: <ClosedMarketPage />,
id: Routes.CLOSED_MARKETS,
},
],
}, },
{ {
path: 'portfolio', path: ':marketId',
element: <Outlet />, element: <MarketPage />,
children: [ id: AppRoutes.MARKET,
{
index: true,
element: <Portfolio />,
id: Routes.PORTFOLIO,
},
{
path: 'assets',
element: <Assets />,
id: Routes.ASSETS,
children: [
{ index: true, element: <Navigate to="deposit" /> },
{ path: 'deposit', element: <Deposit />, id: Routes.DEPOSIT },
{ path: 'withdraw', element: <Withdraw />, id: Routes.WITHDRAW },
{ path: 'transfer', element: <Transfer />, id: Routes.TRANSFER },
],
},
],
},
{
path: 'liquidity/:marketId',
element: <Liquidity />,
id: Routes.LIQUIDITY,
},
// NotFound page is here so its caught within parent '/*' route
{
path: '*',
element: <NotFound />,
}, },
], ],
}, },
{
path: 'portfolio/*',
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
children: [
{
index: true,
element: <Portfolio />,
id: AppRoutes.PORTFOLIO,
},
{
path: 'assets',
element: <Assets />,
id: AppRoutes.ASSETS,
children: [
{ index: true, element: <Navigate to="deposit" /> },
{ path: 'deposit', element: <Deposit />, id: AppRoutes.DEPOSIT },
{ path: 'withdraw', element: <Withdraw />, id: AppRoutes.WITHDRAW },
{ path: 'transfer', element: <Transfer />, id: AppRoutes.TRANSFER },
],
},
],
},
{
path: 'liquidity/*',
element: (
<LayoutWithSidebar
header={<LiquidityHeader />}
sidebar={<LiquiditySidebar />}
/>
),
id: AppRoutes.LIQUIDITY,
children: [
{
path: ':marketId',
element: <Liquidity />,
},
],
},
{
path: '*',
element: <NotFound />,
},
]); ]);
export const ClientRouter = () => { export const ClientRouter = () => {