chore(#1873): trading hash router (#1921)

* chore: make liquidity page client side only

* chore: switch to hash based router

* chore: add index files for each page

* chore: tidy up _app

* chore: convert to use useRoutes

* fix: active state with react-router NavLink

* feat: add routes enum

* chore: restrict link and router imports from next

* chore: update testing navigation to use hash routes

* fix: typoe in eslint rule message

* chore: remove unnecessary getInitialProps function definition

* chore: wrap tests with memory router

* chore: delete unused index.page file

* chore: update suspense fallback state

* chore: add comment for link component span usage, update link to use toolkit styles

* chore: fix lint issues

* chore: delete client deposit page

* chore: revert title in _app so title gets set correctly without rerender

* revert: removal of deposit page so deposit e2e tests still pass

* chore: move client router to index page so valid status codes are still sent

* fix: wrong route path for markets page, cypress tests
This commit is contained in:
Matthew Russell 2022-11-08 01:23:38 -06:00 committed by GitHub
parent 8542f6c5d8
commit c576037b58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 402 additions and 292 deletions

View File

@ -9,7 +9,7 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
cy.mockWeb3Provider(); cy.mockWeb3Provider();
cy.mockGQLSubscription(); cy.mockGQLSubscription();
cy.mockTradingPage(); cy.mockTradingPage();
cy.visit('/portfolio/deposit'); cy.visit('/#/portfolio/deposit');
// Deposit page requires connection Ethereum wallet first // Deposit page requires connection Ethereum wallet first
cy.getByTestId(connectEthWalletBtn).click(); cy.getByTestId(connectEthWalletBtn).click();

View File

@ -11,10 +11,10 @@ describe('vega wallet', { tags: '@smoke' }, () => {
beforeEach(() => { beforeEach(() => {
// Using portfolio page as it requires vega wallet connection // Using portfolio page as it requires vega wallet connection
cy.visit('/portfolio'); cy.visit('/#/portfolio');
cy.mockTradingPage(); cy.mockTradingPage();
cy.mockGQLSubscription(); cy.mockGQLSubscription();
cy.get('main[data-testid="portfolio"]').should('exist'); cy.get('main[data-testid="/portfolio"]').should('exist');
}); });
it('can connect', () => { it('can connect', () => {
@ -79,12 +79,12 @@ describe('ethereum wallet', { tags: '@smoke' }, () => {
beforeEach(() => { beforeEach(() => {
cy.mockWeb3Provider(); cy.mockWeb3Provider();
// Using portfolio withdrawals tab is it requires Ethereum wallet connection // Using portfolio withdrawals tab is it requires Ethereum wallet connection
cy.visit('/portfolio'); cy.visit('/#/portfolio');
cy.mockGQL((req) => { cy.mockGQL((req) => {
aliasQuery(req, 'NetworkParams', generateNetworkParameters()); aliasQuery(req, 'NetworkParams', generateNetworkParameters());
}); });
cy.mockGQLSubscription(); cy.mockGQLSubscription();
cy.get('main[data-testid="portfolio"]').should('exist'); cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click(); cy.getByTestId('Withdrawals').click();
}); });

View File

@ -14,7 +14,9 @@ describe('home', { tags: '@regression' }, () => {
cy.visit('/'); cy.visit('/');
cy.wait('@Market'); cy.wait('@Market');
cy.get('main[data-testid="market"]', { timeout: 20000 }).should('exist'); // Wait for page to be rendered to before checking url cy.get('main', { timeout: 20000 }).then((el) => {
expect(el.attr('data-testid')?.startsWith('/market')).to.equal(true);
}); // Wait for page to be rendered to before checking url
// Overlay should be shown // Overlay should be shown
cy.getByTestId(selectMarketOverlay).should('exist'); cy.getByTestId(selectMarketOverlay).should('exist');
@ -45,7 +47,7 @@ describe('home', { tags: '@regression' }, () => {
// the choose market overlay is no longer showing // the choose market overlay is no longer showing
cy.contains('Select a market to get started').should('not.exist'); cy.contains('Select a market to get started').should('not.exist');
cy.contains('Loading...').should('not.exist'); cy.contains('Loading...').should('not.exist');
cy.url().should('eq', Cypress.config().baseUrl + '/markets/market-0'); cy.url().should('eq', Cypress.config().baseUrl + '/#/markets/market-0');
}); });
}); });
@ -65,7 +67,7 @@ describe('home', { tags: '@regression' }, () => {
cy.visit('/'); cy.visit('/');
cy.wait('@Markets'); cy.wait('@Markets');
cy.wait('@MarketsData'); cy.wait('@MarketsData');
cy.url().should('eq', Cypress.config().baseUrl + '/markets'); cy.url().should('eq', Cypress.config().baseUrl + '/#/markets');
}); });
}); });
}); });

View File

@ -19,7 +19,9 @@ describe('Console - market list - live env', { tags: '@live' }, () => {
}); });
it('shows the market list page', () => { it('shows the market list page', () => {
cy.get('main[data-testid="market"]', { timeout: 20000 }).should('exist'); // Wait for page to be rendered to before checking url cy.get('main', { timeout: 20000 }).then((el) => {
expect(el.attr('data-testid')?.startsWith('/market')).to.equal(true);
}); // Wait for page to be rendered to before checking url
// Overlay should be shown // Overlay should be shown
cy.getByTestId(selectMarketOverlay).should('exist'); cy.getByTestId(selectMarketOverlay).should('exist');

View File

@ -10,7 +10,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
before(() => { before(() => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.mockGQLSubscription(); cy.mockGQLSubscription();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.wait('@Market'); cy.wait('@Market');
cy.getByTestId(marketInfoBtn).click(); cy.getByTestId(marketInfoBtn).click();
cy.wait('@MarketInfo'); cy.wait('@MarketInfo');
@ -222,7 +222,7 @@ describe('market states', { tags: '@smoke' }, function () {
before(function () { before(function () {
cy.mockTradingPage(marketState); cy.mockTradingPage(marketState);
cy.mockGQLSubscription(); cy.mockGQLSubscription();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.wait('@Market'); cy.wait('@Market');
connectVegaWallet(); connectVegaWallet();
}); });

View File

@ -24,7 +24,7 @@ describe('Market trading page', () => {
AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
); );
cy.mockGQLSubscription(); cy.mockGQLSubscription();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.wait('@MarketData'); cy.wait('@MarketData');
cy.getByTestId(marketSummaryBlock).should('be.visible'); cy.getByTestId(marketSummaryBlock).should('be.visible');
}); });

View File

@ -53,9 +53,9 @@ describe('markets table', { tags: '@smoke' }, () => {
'SOLUSD', 'SOLUSD',
]; ];
cy.getByTestId('view-market-list-link') cy.getByTestId('view-market-list-link')
.should('have.attr', 'href', '/markets') .should('have.attr', 'href', '#/markets')
.click(); .click();
cy.url().should('eq', Cypress.config('baseUrl') + '/markets'); cy.url().should('eq', Cypress.config('baseUrl') + '/#/markets');
cy.contains('AAPL.MF21').should('be.visible'); cy.contains('AAPL.MF21').should('be.visible');
cy.contains('Market').click(); // sort by market name cy.contains('Market').click(); // sort by market name
for (let i = 0; i < ExpectedSortedMarkets.length; i++) { for (let i = 0; i < ExpectedSortedMarkets.length; i++) {

View File

@ -5,7 +5,7 @@ beforeEach(() => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.mockWeb3Provider(); cy.mockWeb3Provider();
cy.mockGQLSubscription(); cy.mockGQLSubscription();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
}); });
describe('accounts', { tags: '@smoke' }, () => { describe('accounts', { tags: '@smoke' }, () => {

View File

@ -126,7 +126,7 @@ describe('must submit order', { tags: '@smoke' }, () => {
before(() => { before(() => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.mockGQLSubscription(); cy.mockGQLSubscription();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.wait('@Market'); cy.wait('@Market');
connectVegaWallet(); connectVegaWallet();
}); });
@ -205,7 +205,7 @@ describe('must submit order', { tags: '@smoke' }, () => {
describe('deal ticket validation', { tags: '@smoke' }, () => { describe('deal ticket validation', { tags: '@smoke' }, () => {
beforeEach(() => { beforeEach(() => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.wait('@Market'); cy.wait('@Market');
}); });
@ -253,7 +253,7 @@ describe('deal ticket validation', { tags: '@smoke' }, () => {
describe('deal ticket size validation', { tags: '@smoke' }, function () { describe('deal ticket size validation', { tags: '@smoke' }, function () {
beforeEach(() => { beforeEach(() => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.wait('@Market'); cy.wait('@Market');
connectVegaWallet(); connectVegaWallet();
}); });
@ -284,7 +284,7 @@ describe('deal ticket size validation', { tags: '@smoke' }, function () {
describe('limit order validations', { tags: '@smoke' }, () => { describe('limit order validations', { tags: '@smoke' }, () => {
before(() => { before(() => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.wait('@Market'); cy.wait('@Market');
cy.getByTestId(toggleLimit).click(); cy.getByTestId(toggleLimit).click();
}); });
@ -358,7 +358,7 @@ describe('limit order validations', { tags: '@smoke' }, () => {
describe('market order validations', { tags: '@smoke' }, () => { describe('market order validations', { tags: '@smoke' }, () => {
before(() => { before(() => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.wait('@Market'); cy.wait('@Market');
cy.getByTestId(toggleMarket).click(); cy.getByTestId(toggleMarket).click();
}); });
@ -413,7 +413,7 @@ describe('suspended market validation', { tags: '@regression' }, () => {
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION, MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY
); );
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.wait('@Market'); cy.wait('@Market');
connectVegaWallet(); connectVegaWallet();
}); });
@ -464,7 +464,7 @@ describe('margin required validation', { tags: '@regression' }, () => {
}) })
); );
}); });
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
connectVegaWallet(); connectVegaWallet();
cy.wait('@Market'); cy.wait('@Market');
}); });

View File

@ -12,8 +12,8 @@ describe('fills', { tags: '@regression' }, () => {
}); });
it('renders fills on portfolio page', () => { it('renders fills on portfolio page', () => {
cy.visit('/portfolio'); cy.visit('/#/portfolio');
cy.get('main[data-testid="portfolio"]').should('exist'); cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Fills').click(); cy.getByTestId('Fills').click();
cy.getByTestId('tab-fills').contains('Connect your Vega wallet'); cy.getByTestId('tab-fills').contains('Connect your Vega wallet');
connectVegaWallet(); connectVegaWallet();
@ -22,7 +22,7 @@ describe('fills', { tags: '@regression' }, () => {
it('renders fills on trading tab', () => { it('renders fills on trading tab', () => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.getByTestId('Fills').click(); cy.getByTestId('Fills').click();
cy.getByTestId('tab-fills').contains('Please connect Vega wallet'); cy.getByTestId('tab-fills').contains('Please connect Vega wallet');
connectVegaWallet(); connectVegaWallet();

View File

@ -23,7 +23,7 @@ describe('orders list', { tags: '@smoke' }, () => {
cy.spy(subscriptionMocks, 'OrdersUpdate'); cy.spy(subscriptionMocks, 'OrdersUpdate');
cy.mockTradingPage(); cy.mockTradingPage();
cy.mockGQLSubscription(subscriptionMocks); cy.mockGQLSubscription(subscriptionMocks);
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.getByTestId('Orders').click(); cy.getByTestId('Orders').click();
cy.getByTestId('tab-orders').contains('Please connect Vega wallet'); cy.getByTestId('tab-orders').contains('Please connect Vega wallet');
connectVegaWallet(); connectVegaWallet();
@ -127,7 +127,7 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
cy.spy(subscriptionMocks, 'OrdersUpdate'); cy.spy(subscriptionMocks, 'OrdersUpdate');
cy.mockTradingPage(); cy.mockTradingPage();
cy.mockGQLSubscription(subscriptionMocks); cy.mockGQLSubscription(subscriptionMocks);
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.getByTestId('Orders').click(); cy.getByTestId('Orders').click();
cy.getByTestId('tab-orders').contains('Please connect Vega wallet'); cy.getByTestId('tab-orders').contains('Please connect Vega wallet');
connectVegaWallet(); connectVegaWallet();

View File

@ -8,7 +8,7 @@ beforeEach(() => {
describe('positions', { tags: '@smoke' }, () => { describe('positions', { tags: '@smoke' }, () => {
it('renders positions on trading page', () => { it('renders positions on trading page', () => {
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
cy.getByTestId('Positions').click(); cy.getByTestId('Positions').click();
cy.getByTestId('tab-positions').contains('Please connect Vega wallet'); cy.getByTestId('tab-positions').contains('Please connect Vega wallet');
@ -17,7 +17,7 @@ describe('positions', { tags: '@smoke' }, () => {
}); });
it('renders positions on portfolio page', () => { it('renders positions on portfolio page', () => {
cy.visit('/portfolio'); cy.visit('/#/portfolio');
connectVegaWallet(); connectVegaWallet();
validatePositionsDisplayed(); validatePositionsDisplayed();
}); });

View File

@ -1,7 +1,7 @@
beforeEach(() => { beforeEach(() => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.mockGQLSubscription(); cy.mockGQLSubscription();
cy.visit('/markets/market-0'); cy.visit('/#/markets/market-0');
}); });
describe('trades', { tags: '@smoke' }, () => { describe('trades', { tags: '@smoke' }, () => {

View File

@ -17,7 +17,7 @@ describe('withdraw', { tags: '@smoke' }, () => {
cy.mockTradingPage(); cy.mockTradingPage();
cy.mockGQLSubscription(); cy.mockGQLSubscription();
cy.visit('/portfolio'); cy.visit('/#/portfolio');
cy.getByTestId('Withdrawals').click(); cy.getByTestId('Withdrawals').click();
// Withdraw page requires vega wallet connection // Withdraw page requires vega wallet connection

View File

@ -10,7 +10,18 @@
{ {
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": { "rules": {
"@next/next/no-html-link-for-pages": ["error", "apps/trading/pages"] "@next/next/no-html-link-for-pages": ["error", "apps/trading/pages"],
"no-restricted-imports": [
"error",
{
"name": "next/link",
"message": "Please Link from react-router-dom instead"
},
{
"name": "next/router",
"message": "Please use routing hooks from react-router-dom instead"
}
]
} }
}, },
{ {

View File

@ -0,0 +1,38 @@
import { DepositManager } from '@vegaprotocol/deposits';
import { useDataProvider, t } from '@vegaprotocol/react-helpers';
import { enabledAssetsProvider } from '@vegaprotocol/assets';
import { useEnvironment } from '@vegaprotocol/environment';
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
/**
* Fetches data required for the Deposit page
*/
export const DepositContainer = () => {
const { VEGA_ENV } = useEnvironment();
const { data, error, loading } = useDataProvider({
dataProvider: enabledAssetsProvider,
});
return (
<AsyncRenderer
data={data}
error={error}
loading={loading}
render={(assets) => {
if (!assets || !assets.length) {
return (
<Splash>
<p>{t('No assets on this network')}</p>
</Splash>
);
}
return (
<DepositManager
assets={assets}
isFaucetable={VEGA_ENV !== 'MAINNET'}
/>
);
}}
/>
);
};

View File

@ -5,9 +5,9 @@ import { t, titlefy, useDataProvider } from '@vegaprotocol/react-helpers';
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
import { Web3Container } from '@vegaprotocol/web3'; import { Web3Container } from '@vegaprotocol/web3';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { usePageTitleStore } from '../../../stores'; import { usePageTitleStore } from '../../stores';
const Deposit = () => { export const Deposit = () => {
const { updateTitle } = usePageTitleStore((store) => ({ const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle, updateTitle: store.updateTitle,
})); }));
@ -50,9 +50,3 @@ const Deposit = () => {
</Web3Container> </Web3Container>
); );
}; };
Deposit.getInitialProps = () => ({
page: 'deposit',
});
export default Deposit;

View File

@ -0,0 +1,3 @@
import { Deposit } from './deposit';
export default Deposit;

View File

@ -0,0 +1,63 @@
import { marketsWithDataProvider } from '@vegaprotocol/market-list';
import {
addDecimalsFormatNumber,
titlefy,
useDataProvider,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useGlobalStore, usePageTitleStore } from '../../stores';
export const Home = () => {
const navigate = useNavigate();
// The default market selected in the platform behind the overlay
// should be the oldest market that is currently trading in us mode(i.e. not in auction).
const { data, error, loading } = useDataProvider({
dataProvider: marketsWithDataProvider,
});
const { riskNoticeDialog, update } = useGlobalStore((store) => ({
riskNoticeDialog: store.riskNoticeDialog,
update: store.update,
}));
const { pageTitle, updateTitle } = usePageTitleStore((store) => ({
pageTitle: store.pageTitle,
updateTitle: store.updateTitle,
}));
useEffect(() => {
update({ landingDialog: true });
if (data) {
const marketId = data[0]?.id;
const marketName = data[0]?.tradableInstrument.instrument.name;
const marketPrice = data[0]?.data?.markPrice
? addDecimalsFormatNumber(
data[0]?.data?.markPrice,
data[0]?.decimalPlaces
)
: null;
const newPageTitle = titlefy([marketName, marketPrice]);
if (marketId) {
navigate(`/markets/${marketId}`, { replace: true });
update({ marketId });
if (pageTitle !== newPageTitle) {
updateTitle(newPageTitle);
}
}
// Fallback to the markets list page
else {
navigate('/markets');
}
}
}, [data, navigate, riskNoticeDialog, update, pageTitle, updateTitle]);
return (
<AsyncRenderer data={data} loading={loading} error={error}>
{/* Render a loading and error state but we will redirect if markets are found */}
{null}
</AsyncRenderer>
);
};

View File

@ -0,0 +1,3 @@
import { Home } from './home';
export default Home;

View File

@ -0,0 +1,3 @@
import { Liquidity } from './liquidity';
export default Liquidity;

View File

@ -15,28 +15,25 @@ import {
import { Schema } from '@vegaprotocol/types'; import { Schema } from '@vegaprotocol/types';
import { import {
AsyncRenderer, AsyncRenderer,
Link as UiToolkitLink,
Tab, Tab,
Tabs, Tabs,
Link as UiToolkitLink,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet'; import { useVegaWallet } from '@vegaprotocol/wallet';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react';
import { Header, HeaderStat } from '../../components/header'; import { Header, HeaderStat } from '../../components/header';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import type { LiquidityProvisionData } from '@vegaprotocol/liquidity'; import type { LiquidityProvisionData } from '@vegaprotocol/liquidity';
import { Link, useParams } from 'react-router-dom';
const LiquidityPage = ({ id }: { id?: string }) => { export const Liquidity = () => {
const { query } = useRouter(); const params = useParams();
const { pubKey } = useVegaWallet(); const { pubKey } = useVegaWallet();
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
// Default to first marketId query item if found const marketId = params.marketId;
const marketId =
id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId);
const { data: marketProvision } = useDataProvider({ const { data: marketProvision } = useDataProvider({
dataProvider: marketLiquidityDataProvider, dataProvider: marketLiquidityDataProvider,
@ -139,7 +136,7 @@ const LiquidityPage = ({ id }: { id?: string }) => {
<div className="h-full grid grid-rows-[min-content_1fr]"> <div className="h-full grid grid-rows-[min-content_1fr]">
<Header <Header
title={ title={
<Link href={`/markets/${marketId}`} passHref={true}> <Link to={`/markets/${marketId}`}>
<UiToolkitLink className="sm:text-lg md:text-xl lg:text-2xl flex items-center gap-2 whitespace-nowrap hover:text-neutral-500 dark:hover:text-neutral-300"> <UiToolkitLink className="sm:text-lg md:text-xl lg:text-2xl flex items-center gap-2 whitespace-nowrap hover:text-neutral-500 dark:hover:text-neutral-300">
{`${ {`${
marketProvision?.market?.tradableInstrument.instrument.name marketProvision?.market?.tradableInstrument.instrument.name
@ -223,8 +220,3 @@ const LiquidityPage = ({ id }: { id?: string }) => {
</AsyncRenderer> </AsyncRenderer>
); );
}; };
LiquidityPage.getInitialProps = () => ({
page: 'liquidity',
});
export default LiquidityPage;

View File

@ -0,0 +1,3 @@
import { Market } from './market';
export default Market;

View File

@ -8,7 +8,6 @@ import {
useDataProvider, useDataProvider,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
import { useRouter } from 'next/router';
import type { import type {
SingleMarketFieldsFragment, SingleMarketFieldsFragment,
MarketData, MarketData,
@ -19,6 +18,7 @@ import { marketProvider, marketDataProvider } from '@vegaprotocol/market-list';
import { useGlobalStore, usePageTitleStore } from '../../stores'; import { useGlobalStore, usePageTitleStore } from '../../stores';
import { TradeGrid, TradePanels } from './trade-grid'; import { TradeGrid, TradePanels } from './trade-grid';
import { ColumnKind, SelectMarketDialog } from '../../components/select-market'; import { ColumnKind, SelectMarketDialog } from '../../components/select-market';
import { useNavigate, useParams } from 'react-router-dom';
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => { const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
return markPrice && decimalPlaces return markPrice && decimalPlaces
@ -31,14 +31,16 @@ export interface SingleMarketData extends SingleMarketFieldsFragment {
data: MarketData; data: MarketData;
} }
const MarketPage = ({ export const Market = ({
id, id,
marketId: mid, marketId: mid,
}: { }: {
id?: string; id?: string;
marketId?: string; marketId?: string;
}) => { }) => {
const { query, push } = useRouter(); const params = useParams();
const navigate = useNavigate();
const marketId = params.marketId;
const { w } = useWindowSize(); const { w } = useWindowSize();
const { landingDialog, riskNoticeDialog, update } = useGlobalStore( const { landingDialog, riskNoticeDialog, update } = useGlobalStore(
(store) => ({ (store) => ({
@ -55,18 +57,14 @@ const MarketPage = ({
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore(); const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
// Default to first marketId query item if found
const marketId =
id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId);
const onSelect = useCallback( const onSelect = useCallback(
(id: string) => { (id: string) => {
if (id && id !== marketId) { if (id && id !== marketId) {
update({ marketId: id }); update({ marketId: id });
push(`/markets/${id}`); navigate(`/markets/${id}`);
} }
}, },
[marketId, update, push] [marketId, update, navigate]
); );
const variables = useMemo( const variables = useMemo(
@ -160,12 +158,6 @@ const MarketPage = ({
); );
}; };
MarketPage.getInitialProps = () => ({
page: 'market',
});
export default MarketPage;
const useWindowSize = () => { const useWindowSize = () => {
const [windowSize, setWindowSize] = useState(() => { const [windowSize, setWindowSize] = useState(() => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {

View File

@ -24,7 +24,7 @@ import { t } from '@vegaprotocol/react-helpers';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { Header, HeaderStat } from '../../components/header'; import { Header, HeaderStat } from '../../components/header';
import { AccountsContainer } from '../portfolio/accounts-container'; import { AccountsContainer } from '../../components/accounts-container';
import { import {
ColumnKind, ColumnKind,
SelectMarketPopover, SelectMarketPopover,

View File

@ -0,0 +1,3 @@
import { Markets } from './markets';
export default Markets;

View File

@ -1,10 +1,11 @@
import { useRouter } from 'next/router';
import { MarketsContainer } from '@vegaprotocol/market-list'; import { MarketsContainer } from '@vegaprotocol/market-list';
import { useGlobalStore, usePageTitleStore } from '../../stores'; import { useGlobalStore, usePageTitleStore } from '../../stores';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { titlefy } from '@vegaprotocol/react-helpers'; import { titlefy } from '@vegaprotocol/react-helpers';
import { useNavigate } from 'react-router-dom';
const Markets = () => { export const Markets = () => {
const navigate = useNavigate();
const { update } = useGlobalStore((store) => ({ update: store.update })); const { update } = useGlobalStore((store) => ({ update: store.update }));
const { updateTitle } = usePageTitleStore((store) => ({ const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle, updateTitle: store.updateTitle,
@ -12,20 +13,13 @@ const Markets = () => {
useEffect(() => { useEffect(() => {
updateTitle(titlefy(['Markets'])); updateTitle(titlefy(['Markets']));
}, [updateTitle]); }, [updateTitle]);
const router = useRouter();
return ( return (
<MarketsContainer <MarketsContainer
onSelect={(marketId) => { onSelect={(marketId) => {
update({ marketId }); update({ marketId });
router.push(`/markets/${marketId}`); navigate(`/markets/${marketId}`);
}} }}
/> />
); );
}; };
Markets.getInitialProps = () => ({
page: 'markets',
});
export default Markets;

View File

@ -0,0 +1,3 @@
import { Portfolio } from './portfolio';
export default Portfolio;

View File

@ -11,16 +11,18 @@ import { DepositsContainer } from './deposits-container';
import { ResizableGrid } from '@vegaprotocol/ui-toolkit'; import { ResizableGrid } from '@vegaprotocol/ui-toolkit';
import { LayoutPriority } from 'allotment'; import { LayoutPriority } from 'allotment';
import { usePageTitleStore } from '../../stores'; import { usePageTitleStore } from '../../stores';
import { AccountsContainer } from './accounts-container';
import { LedgerContainer } from '@vegaprotocol/ledger'; import { LedgerContainer } from '@vegaprotocol/ledger';
import { AccountsContainer } from '../../components/accounts-container';
const Portfolio = () => { export const Portfolio = () => {
const { updateTitle } = usePageTitleStore((store) => ({ const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle, updateTitle: store.updateTitle,
})); }));
useEffect(() => { useEffect(() => {
updateTitle(titlefy([t('Portfolio')])); updateTitle(titlefy([t('Portfolio')]));
}, [updateTitle]); }, [updateTitle]);
const wrapperClasses = 'h-full max-h-full flex flex-col'; const wrapperClasses = 'h-full max-h-full flex flex-col';
return ( return (
<div className={wrapperClasses}> <div className={wrapperClasses}>
@ -79,12 +81,6 @@ const Portfolio = () => {
); );
}; };
Portfolio.getInitialProps = () => ({
page: 'portfolio',
});
export default Portfolio;
interface PortfolioGridChildProps { interface PortfolioGridChildProps {
children: ReactNode; children: ReactNode;
} }

View File

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

View File

@ -1,6 +1,5 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { useRouter } from 'next/router'; import { NavLink, Link } from 'react-router-dom';
import Link from 'next/link';
import { NetworkSwitcher, useEnvironment } from '@vegaprotocol/environment'; import { NetworkSwitcher, useEnvironment } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import { useGlobalStore } from '../../stores/global'; import { useGlobalStore } from '../../stores/global';
@ -8,8 +7,8 @@ import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit'; import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
import { Vega } from '../icons/vega'; import { Vega } from '../icons/vega';
import type { HTMLAttributeAnchorTarget } from 'react'; import type { HTMLAttributeAnchorTarget } from 'react';
import { useEffect, useState } from 'react';
import testnetBg from '../../assets/green-cloud.png'; import testnetBg from '../../assets/green-cloud.png';
import { Routes } from '../../pages/client-router';
type NavbarTheme = 'inherit' | 'dark' | 'yellow'; type NavbarTheme = 'inherit' | 'dark' | 'yellow';
interface NavbarProps { interface NavbarProps {
@ -27,13 +26,7 @@ export const Navbar = ({
const { marketId } = useGlobalStore((store) => ({ const { marketId } = useGlobalStore((store) => ({
marketId: store.marketId, marketId: store.marketId,
})); }));
const [tradingPath, setTradingPath] = useState('/markets'); const tradingPath = marketId ? `/markets/${marketId}` : '/markets';
useEffect(() => {
if (marketId) {
setTradingPath(`/markets/${marketId}`);
}
}, [marketId]);
const themeWrapperClasses = classNames({ const themeWrapperClasses = classNames({
dark: navbarTheme === 'dark', dark: navbarTheme === 'dark',
@ -58,26 +51,23 @@ export const Navbar = ({
}} }}
> >
<div className="flex gap-4 items-center"> <div className="flex gap-4 items-center">
<Link href="/" passHref={true}> <Link to="/">
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} <Vega className="w-13" />
<a>
<Vega className="w-13" />
</a>
</Link> </Link>
<NetworkSwitcher /> <NetworkSwitcher />
</div> </div>
<nav className="flex items-center flex-1 px-2"> <nav className="flex items-center flex-1 px-2">
<NavLink <AppNavLink
name={t('Trading')} name={t('Trading')}
path={tradingPath} path={tradingPath}
navbarTheme={navbarTheme} navbarTheme={navbarTheme}
/> />
<NavLink <AppNavLink
name={t('Portfolio')} name={t('Portfolio')}
path="/portfolio" path={Routes.PORTFOLIO}
navbarTheme={navbarTheme} navbarTheme={navbarTheme}
/> />
<NavLink <AppNavLink
name={t('Governance')} name={t('Governance')}
path={`${VEGA_TOKEN_URL}/governance`} path={`${VEGA_TOKEN_URL}/governance`}
alignRight={true} alignRight={true}
@ -94,7 +84,7 @@ export const Navbar = ({
); );
}; };
interface NavLinkProps { interface AppNavLinkProps {
name: string; name: string;
path: string; path: string;
navbarTheme: NavbarTheme; navbarTheme: NavbarTheme;
@ -103,36 +93,44 @@ interface NavLinkProps {
target?: HTMLAttributeAnchorTarget; target?: HTMLAttributeAnchorTarget;
} }
const NavLink = ({ const AppNavLink = ({
name, name,
path, path,
navbarTheme, navbarTheme,
alignRight, alignRight,
target, target,
testId = name, testId = name,
}: NavLinkProps) => { }: AppNavLinkProps) => {
const router = useRouter();
const isActive = router.asPath?.includes(path);
const linkClasses = classNames('mx-2 py-3 self-end relative', {
'cursor-default': isActive,
'text-black dark:text-white': isActive && navbarTheme !== 'yellow',
'text-neutral-500 dark:text-neutral-400 hover:text-black dark:hover:text-neutral-300':
!isActive && navbarTheme !== 'yellow',
'ml-auto': alignRight,
'text-black': isActive && navbarTheme === 'yellow',
'text-black/60 hover:text-black': !isActive && navbarTheme === 'yellow',
});
const borderClasses = classNames('absolute h-1 w-full bottom-[-1px] left-0', { const borderClasses = classNames('absolute h-1 w-full bottom-[-1px] left-0', {
'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow', 'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow',
'bg-black': navbarTheme === 'yellow', 'bg-black': navbarTheme === 'yellow',
}); });
return ( return (
<Link data-testid={testId} href={path} passHref={true}> <NavLink
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} data-testid={testId}
<a className={linkClasses} target={target}> to={path}
{name} className={({ isActive }) => {
{isActive && <span className={borderClasses} />} return classNames('mx-2 py-3 self-end relative', {
</a> 'cursor-default': isActive,
</Link> 'text-black dark:text-white': isActive && navbarTheme !== 'yellow',
'text-neutral-500 dark:text-neutral-400 hover:text-black dark:hover:text-neutral-300':
!isActive && navbarTheme !== 'yellow',
'ml-auto': alignRight,
'text-black': isActive && navbarTheme === 'yellow',
'text-black/60 hover:text-black':
!isActive && navbarTheme === 'yellow',
});
}}
target={target}
>
{({ isActive }) => {
return (
<>
{name}
{isActive && <span className={borderClasses} />}
</>
);
}}
</NavLink>
); );
}; };

View File

@ -19,7 +19,7 @@ import {
MarketTradingModeMapping, MarketTradingModeMapping,
} from '@vegaprotocol/types'; } from '@vegaprotocol/types';
import { import {
Link, Link as UILink,
PriceCellChange, PriceCellChange,
Sparkline, Sparkline,
Tooltip, Tooltip,
@ -31,6 +31,8 @@ import type {
MarketWithData, MarketWithData,
MarketWithCandles, MarketWithCandles,
} from '@vegaprotocol/market-list'; } from '@vegaprotocol/market-list';
import { Link } from 'react-router-dom';
type Market = MarketWithData & MarketWithCandles; type Market = MarketWithData & MarketWithCandles;
export const cellClassNames = 'py-1 first:text-left text-right'; export const cellClassNames = 'py-1 first:text-left text-right';
@ -224,7 +226,7 @@ export const columns = (
kind: ColumnKind.Market, kind: ColumnKind.Market,
value: ( value: (
<Link <Link
href={`/markets/${market.id}`} to={`/markets/${market.id}`}
data-testid={`market-link-${market.id}`} data-testid={`market-link-${market.id}`}
onKeyPress={(event) => handleKeyPress(event, market.id)} onKeyPress={(event) => handleKeyPress(event, market.id)}
onClick={(e) => { onClick={(e) => {
@ -232,7 +234,7 @@ export const columns = (
onSelect(market.id); onSelect(market.id);
}} }}
> >
{market.tradableInstrument.instrument.code} <UILink>{market.tradableInstrument.instrument.code}</UILink>
</Link> </Link>
), ),
className: cellClassNames, className: cellClassNames,
@ -392,7 +394,7 @@ export const columnsPositionMarkets = (
const candleLow = market.candles && calcCandleLow(market.candles); const candleLow = market.candles && calcCandleLow(market.candles);
const candleHigh = market.candles && calcCandleHigh(market.candles); const candleHigh = market.candles && calcCandleHigh(market.candles);
const handleKeyPress = ( const handleKeyPress = (
event: React.KeyboardEvent<HTMLAnchorElement>, event: React.KeyboardEvent<HTMLSpanElement>,
id: string id: string
) => { ) => {
if (event.key === 'Enter' && onSelect) { if (event.key === 'Enter' && onSelect) {
@ -405,7 +407,7 @@ export const columnsPositionMarkets = (
kind: ColumnKind.Market, kind: ColumnKind.Market,
value: ( value: (
<Link <Link
href={`/markets/${market.id}`} to={`/markets/${market.id}`}
data-testid={`market-link-${market.id}`} data-testid={`market-link-${market.id}`}
onKeyPress={(event) => handleKeyPress(event, market.id)} onKeyPress={(event) => handleKeyPress(event, market.id)}
onClick={(e) => { onClick={(e) => {
@ -413,7 +415,7 @@ export const columnsPositionMarkets = (
onSelect(market.id); onSelect(market.id);
}} }}
> >
{market.tradableInstrument.instrument.code} <UILink>{market.tradableInstrument.instrument.code}</UILink>
</Link> </Link>
), ),
className: cellClassNames, className: cellClassNames,

View File

@ -6,21 +6,14 @@ import {
SelectMarketLandingTable, SelectMarketLandingTable,
} from './select-market'; } from './select-market';
import type { ReactNode } from 'react';
import type { import type {
MarketWithCandles, MarketWithCandles,
MarketWithData, MarketWithData,
MarketData, MarketData,
} from '@vegaprotocol/market-list'; } from '@vegaprotocol/market-list';
import { MemoryRouter } from 'react-router-dom';
type Market = MarketWithCandles & MarketWithData; type Market = MarketWithCandles & MarketWithData;
jest.mock(
'next/link',
() =>
({ children }: { children: ReactNode }) =>
children
);
type PartialMarket = Partial< type PartialMarket = Partial<
Omit<Market, 'data'> & { data: Partial<MarketData> } Omit<Market, 'data'> & { data: Partial<MarketData> }
>; >;
@ -157,11 +150,13 @@ describe('SelectMarket', () => {
const onSelect = jest.fn(); const onSelect = jest.fn();
const onCellClick = jest.fn(); const onCellClick = jest.fn();
const { container } = render( const { container } = render(
<SelectAllMarketsTableBody <MemoryRouter>
markets={[MARKET_A as Market, MARKET_B as Market]} <SelectAllMarketsTableBody
onCellClick={onCellClick} markets={[MARKET_A as Market, MARKET_B as Market]}
onSelect={onSelect} onCellClick={onCellClick}
/> onSelect={onSelect}
/>
</MemoryRouter>
); );
expect(screen.getByText('ABCDEF')).toBeTruthy(); // name expect(screen.getByText('ABCDEF')).toBeTruthy(); // name
expect(screen.getByText('25.00%')).toBeTruthy(); // price change expect(screen.getByText('25.00%')).toBeTruthy(); // price change
@ -175,11 +170,13 @@ describe('SelectMarket', () => {
const onCellClick = jest.fn(); const onCellClick = jest.fn();
render( render(
<SelectMarketLandingTable <MemoryRouter>
markets={[MARKET_A as Market, MARKET_B as Market]} <SelectMarketLandingTable
onCellClick={onCellClick} markets={[MARKET_A as Market, MARKET_B as Market]}
onSelect={onSelect} onCellClick={onCellClick}
/> onSelect={onSelect}
/>
</MemoryRouter>
); );
fireEvent.click(screen.getAllByTestId(`market-link-1`)[0]); fireEvent.click(screen.getAllByTestId(`market-link-1`)[0]);
expect(onSelect).toHaveBeenCalledWith('1'); expect(onSelect).toHaveBeenCalledWith('1');

View File

@ -5,7 +5,7 @@ import {
Dialog, Dialog,
Icon, Icon,
Intent, Intent,
Link, Link as UILink,
Loader, Loader,
Popover, Popover,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
@ -30,6 +30,7 @@ import type {
} from '@vegaprotocol/market-list'; } from '@vegaprotocol/market-list';
import type { PositionFieldsFragment } from '@vegaprotocol/positions'; import type { PositionFieldsFragment } from '@vegaprotocol/positions';
import type { Column, OnCellClickHandler } from './select-market-columns'; import type { Column, OnCellClickHandler } from './select-market-columns';
import { Link } from 'react-router-dom';
type Market = MarketWithCandles & MarketWithData; type Market = MarketWithCandles & MarketWithData;
export const SelectMarketLandingTable = ({ export const SelectMarketLandingTable = ({
@ -65,8 +66,8 @@ export const SelectMarketLandingTable = ({
</table> </table>
</div> </div>
<div className="mt-4 text-md"> <div className="mt-4 text-md">
<Link href="/markets" data-testid="view-market-list-link"> <Link to="/markets" data-testid="view-market-list-link">
{'Or view full market list'} <UILink>{'Or view full market list'} </UILink>
</Link> </Link>
</div> </div>
</> </>

View File

@ -1,5 +1,5 @@
import type { AppProps } from 'next/app';
import Head from 'next/head'; import Head from 'next/head';
import type { AppProps } from 'next/app';
import { Navbar } from '../components/navbar'; import { Navbar } from '../components/navbar';
import { t, ThemeContext, useThemeSwitcher } from '@vegaprotocol/react-helpers'; import { t, ThemeContext, useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { VegaWalletProvider } from '@vegaprotocol/wallet'; import { VegaWalletProvider } from '@vegaprotocol/wallet';
@ -13,8 +13,9 @@ import { AppLoader } from '../components/app-loader';
import './styles.css'; import './styles.css';
import { usePageTitleStore } from '../stores'; import { usePageTitleStore } from '../stores';
import { Footer } from '../components/footer'; import { Footer } from '../components/footer';
import { useMemo } from 'react'; import { useEffect, useMemo, useState } from 'react';
import DialogsContainer from './dialogs-container'; import DialogsContainer from './dialogs-container';
import { HashRouter, useLocation } from 'react-router-dom';
const DEFAULT_TITLE = t('Welcome to Vega trading!'); const DEFAULT_TITLE = t('Welcome to Vega trading!');
@ -31,6 +32,7 @@ const Title = () => {
if (networkName) return `${pageTitle} [${networkName}]`; if (networkName) return `${pageTitle} [${networkName}]`;
return pageTitle; return pageTitle;
}, [pageTitle, networkName]); }, [pageTitle, networkName]);
return ( return (
<Head> <Head>
<title>{title}</title> <title>{title}</title>
@ -38,9 +40,11 @@ const Title = () => {
); );
}; };
function AppBody({ Component, pageProps }: AppProps) { function AppBody({ Component }: AppProps) {
const location = useLocation();
const { VEGA_ENV } = useEnvironment(); const { VEGA_ENV } = useEnvironment();
const [theme, toggleTheme] = useThemeSwitcher(); const [theme, toggleTheme] = useThemeSwitcher();
return ( return (
<ThemeContext.Provider value={theme}> <ThemeContext.Provider value={theme}>
<Head> <Head>
@ -54,8 +58,8 @@ function AppBody({ Component, pageProps }: AppProps) {
toggleTheme={toggleTheme} toggleTheme={toggleTheme}
navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'}
/> />
<main data-testid={pageProps.page}> <main data-testid={location.pathname}>
<Component {...pageProps} /> <Component />
</main> </main>
<Footer /> <Footer />
<DialogsContainer /> <DialogsContainer />
@ -66,12 +70,25 @@ function AppBody({ Component, pageProps }: AppProps) {
} }
function VegaTradingApp(props: AppProps) { function VegaTradingApp(props: AppProps) {
const [mounted, setMounted] = useState(false);
// Hash router requires access to the document object. At compile time that doesn't exist
// so we need to ensure client side rendering only from this point onwards in
// the component tree
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
return ( return (
<EnvironmentProvider> <HashRouter>
<VegaWalletProvider> <EnvironmentProvider>
<AppBody {...props} /> <VegaWalletProvider>
</VegaWalletProvider> <AppBody {...props} />
</EnvironmentProvider> </VegaWalletProvider>
</EnvironmentProvider>
</HashRouter>
); );
} }

View File

@ -4,6 +4,7 @@ export default function Document() {
return ( return (
<Html> <Html>
<Head> <Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link <link
rel="preload" rel="preload"
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2" href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"

View File

@ -0,0 +1,77 @@
import { Suspense } from 'react';
import { useRoutes } from 'react-router-dom';
import dynamic from 'next/dynamic';
import { t } from '@vegaprotocol/react-helpers';
const LazyHome = dynamic(() => import('../client-pages/home'), {
ssr: false,
});
const LazyLiquidity = dynamic(() => import('../client-pages/liquidity'), {
ssr: false,
});
const LazyMarkets = dynamic(() => import('../client-pages/markets'), {
ssr: false,
});
const LazyMarket = dynamic(() => import('../client-pages/market'), {
ssr: false,
});
const LazyPortfolio = dynamic(() => import('../client-pages/portfolio'), {
ssr: false,
});
const LazyDeposit = dynamic(() => import('../client-pages/deposit'), {
ssr: false,
});
export enum Routes {
HOME = '/',
MARKETS = '/markets',
PORTFOLIO = '/portfolio',
PORTFOLIO_DEPOSIT = '/portfolio/deposit',
}
const routerConfig = [
{
index: true,
element: <LazyHome />,
},
{
path: Routes.MARKETS,
element: <LazyMarkets />,
},
{
path: 'markets/:marketId',
element: <LazyMarket />,
},
{
path: 'liquidity/:marketId',
element: <LazyLiquidity />,
},
{
path: Routes.PORTFOLIO,
element: <LazyPortfolio />,
},
{
path: Routes.PORTFOLIO_DEPOSIT,
element: <LazyDeposit />,
},
];
export const ClientRouter = () => {
const routes = useRoutes(routerConfig);
return (
<Suspense
fallback={
<div className="w-full h-full flex justify-center items-center">
{t('Loading...')}
</div>
}
>
{routes}
</Suspense>
);
};

View File

@ -1,69 +1,10 @@
import { marketsWithDataProvider } from '@vegaprotocol/market-list'; import { ClientRouter } from './client-router';
import {
addDecimalsFormatNumber,
titlefy,
useDataProvider,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { useGlobalStore, usePageTitleStore } from '../stores';
export function Index() { /**
const { replace } = useRouter(); * Next only handles this single index page, react-router takes over after the page
// The default market selected in the platform behind the overlay * is served to the browser. This is because we can't run next server via ipfs and
// should be the oldest market that is currently trading in us mode(i.e. not in auction). * have to serve a static site via next export
const { data, error, loading } = useDataProvider({ */
dataProvider: marketsWithDataProvider, export default function Index() {
}); return <ClientRouter />;
const { riskNoticeDialog, update } = useGlobalStore((store) => ({
riskNoticeDialog: store.riskNoticeDialog,
update: store.update,
}));
const { pageTitle, updateTitle } = usePageTitleStore((store) => ({
pageTitle: store.pageTitle,
updateTitle: store.updateTitle,
}));
useEffect(() => {
update({ landingDialog: true });
if (data) {
const marketId = data[0]?.id;
const marketName = data[0]?.tradableInstrument.instrument.name;
const marketPrice = data[0]?.data?.markPrice
? addDecimalsFormatNumber(
data[0]?.data?.markPrice,
data[0]?.decimalPlaces
)
: null;
const newPageTitle = titlefy([marketName, marketPrice]);
if (marketId) {
replace(`/markets/${marketId}`);
update({ marketId });
if (pageTitle !== newPageTitle) {
updateTitle(newPageTitle);
}
}
// Fallback to the markets list page
else {
replace('/markets');
}
}
}, [data, replace, riskNoticeDialog, update, pageTitle, updateTitle]);
return (
<AsyncRenderer data={data} loading={loading} error={error}>
{/* Render a loading and error state but we will redirect if markets are found */}
{null}
</AsyncRenderer>
);
} }
Index.getInitialProps = () => ({
page: 'home',
});
export default Index;

View File

@ -1 +0,0 @@
export * from './[marketId].page';

View File

@ -1,36 +0,0 @@
import React from 'react';
import { act, render } from '@testing-library/react';
import Index from '../pages/index.page';
import { MockedProvider } from '@apollo/react-testing';
jest.mock('@vegaprotocol/ui-toolkit', () => {
const original = jest.requireActual('@vegaprotocol/ui-toolkit');
return {
...original,
AgGridDynamic: () => <div>AgGrid</div>,
};
});
jest.mock('next/router', () => ({
useRouter() {
return {
route: '/',
pathname: '',
query: '',
asPath: '',
};
},
}));
describe('Index', () => {
it('should render successfully', async () => {
act(() => {
const { baseElement } = render(
<MockedProvider>
<Index />
</MockedProvider>
);
expect(baseElement).toBeTruthy();
});
});
});

View File

@ -6,6 +6,7 @@ const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom
module.exports = { module.exports = {
content: [ content: [
join(__dirname, 'pages/**/*.{js,ts,jsx,tsx}'), join(__dirname, 'pages/**/*.{js,ts,jsx,tsx}'),
join(__dirname, 'client-pages/**/*.{js,ts,jsx,tsx}'),
join(__dirname, 'components/**/*.{js,ts,jsx,tsx}'), join(__dirname, 'components/**/*.{js,ts,jsx,tsx}'),
'libs/ui-toolkit/src/utils/shared.ts', 'libs/ui-toolkit/src/utils/shared.ts',
...createGlobPatternsForDependencies(__dirname), ...createGlobPatternsForDependencies(__dirname),

View File

@ -3,12 +3,12 @@ import {
getDateTimeFormat, getDateTimeFormat,
addDecimalsFormatNumber, addDecimalsFormatNumber,
} from '@vegaprotocol/react-helpers'; } from '@vegaprotocol/react-helpers';
import { Link as UiToolkitLink } from '@vegaprotocol/ui-toolkit';
import Link from 'next/link';
import { MarketTradingMode, AuctionTrigger } from '@vegaprotocol/types'; import { MarketTradingMode, AuctionTrigger } from '@vegaprotocol/types';
import { Link as UILink } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { MarketDataGridProps } from './market-data-grid'; import type { MarketDataGridProps } from './market-data-grid';
import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket'; import type { DealTicketMarketFragment } from '../deal-ticket/__generated___/DealTicket';
import { Link } from 'react-router-dom';
export const compileGridData = ( export const compileGridData = (
market: Omit<DealTicketMarketFragment, 'depth'>, market: Omit<DealTicketMarketFragment, 'depth'>,
@ -60,10 +60,11 @@ export const compileGridData = (
if (isLiquidityMonitoringAuction && market.data?.suppliedStake) { if (isLiquidityMonitoringAuction && market.data?.suppliedStake) {
grid.push({ grid.push({
label: ( label: (
<Link href={`/liquidity/${market.id}`} passHref={true}> <Link
<UiToolkitLink onClick={() => onSelect && onSelect(market.id)}> to={`/liquidity/${market.id}`}
{t('Current liquidity')} onClick={() => onSelect && onSelect(market.id)}
</UiToolkitLink> >
<UILink>{t('Current liquidity')}</UILink>
</Link> </Link>
), ),
value: formatStake(market.data.suppliedStake), value: formatStake(market.data.suppliedStake),

View File

@ -17,14 +17,13 @@ import {
Accordion, Accordion,
AsyncRenderer, AsyncRenderer,
ExternalLink, ExternalLink,
Link as UiToolkitLink, Link as UILink,
Splash, Splash,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
import Link from 'next/link';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { generatePath } from 'react-router-dom'; import { generatePath, Link } from 'react-router-dom';
import { getMarketExpiryDateFormatted } from '../market-expires'; import { getMarketExpiryDateFormatted } from '../market-expires';
import { MarketInfoTable } from './info-key-value-table'; import { MarketInfoTable } from './info-key-value-table';
@ -319,13 +318,12 @@ export const Info = ({ market, onSelect }: InfoProps) => {
} }
assetSymbol={assetSymbol} assetSymbol={assetSymbol}
> >
<Link passHref={true} href={`/liquidity/${market.id}`}> <Link
<UiToolkitLink to={`/liquidity/${market.id}`}
onClick={() => onSelect(market.id)} onClick={() => onSelect(market.id)}
data-testid="view-liquidity-link" data-testid="view-liquidity-link"
> >
{t('View liquidity provision table')} <UILink>{t('View liquidity provision table')}</UILink>
</UiToolkitLink>
</Link> </Link>
</MarketInfoTable> </MarketInfoTable>
), ),

View File

@ -16,15 +16,25 @@ export const Link = ({ className, children, ...props }: LinkProps) => {
'cursor-pointer': props['aria-disabled'] !== true, 'cursor-pointer': props['aria-disabled'] !== true,
'opacity-50 pointer-events-none': props['aria-disabled'] === true, 'opacity-50 pointer-events-none': props['aria-disabled'] === true,
}); });
const shared = {
role: 'link',
'data-testid': 'link',
referrerPolicy: 'strict-origin' as const,
className: anchorClassName,
};
// if no href is passed just render a span, this is so that we can wrap an
// element with our links styles with a react router link compoment
if (!props.href) {
return (
<span {...shared} {...props}>
{children}
</span>
);
}
return ( return (
<a <a {...shared} {...props}>
role="link"
data-testid="link"
referrerPolicy="strict-origin"
className={anchorClassName}
{...props}
>
{children} {children}
</a> </a>
); );