diff --git a/apps/trading-e2e/src/support/pages/base-page.ts b/apps/trading-e2e/src/support/pages/base-page.ts index bc5861d6f..85aeebc68 100644 --- a/apps/trading-e2e/src/support/pages/base-page.ts +++ b/apps/trading-e2e/src/support/pages/base-page.ts @@ -1,6 +1,6 @@ export default class BasePage { closeDialogBtn = 'dialog-close'; - porfolioUrl = '/portfolio'; + portfolioUrl = '/portfolio'; marketsUrl = '/markets'; assetSelectField = 'select[name="asset"]'; toAddressField = 'input[name="to"]'; @@ -10,21 +10,22 @@ export default class BasePage { dialogText = 'dialog-text'; closeDialog() { - cy.getByTestId(this.closeDialogBtn, { timeout: 8000 }).click({ + cy.getByTestId(this.closeDialogBtn, { timeout: 8000 })?.click({ force: true, }); } navigateToPortfolio() { - cy.get(`a[href='${this.porfolioUrl}']`).should('be.visible').click(); + cy.get(`a[href='${this.portfolioUrl}']`) + .should('be.visible') + .click({ force: true }); cy.url().should('include', '/portfolio'); cy.getByTestId('portfolio'); } navigateToMarkets() { - cy.get(`a[href='${this.marketsUrl}']`).should('be.visible').click(); + cy.getByTestId('markets-link').should('be.visible').click({ force: true }); cy.url().should('include', '/markets'); - cy.getByTestId('markets'); } verifyFormErrorDisplayed(expectedError: string, expectedNumErrors: number) { @@ -35,7 +36,7 @@ export default class BasePage { ); } - updateTransactionform(args?: { + updateTransactionForm(args?: { asset?: string; to?: string; amount?: string; diff --git a/apps/trading-e2e/src/support/pages/markets-page.ts b/apps/trading-e2e/src/support/pages/markets-page.ts index 7d16abd0f..b787da89b 100644 --- a/apps/trading-e2e/src/support/pages/markets-page.ts +++ b/apps/trading-e2e/src/support/pages/markets-page.ts @@ -1,13 +1,14 @@ import BasePage from './base-page'; export default class MarketPage extends BasePage { - marketRowHeaderClassname = '.ag-header-cell-text'; + marketRowHeaderClassname = 'div > span.ag-header-cell-text'; marketRowNameColumn = 'tradableInstrument.instrument.code'; marketRowSymbolColumn = 'tradableInstrument.instrument.product.settlementAsset.symbol'; marketRowPrices = 'flash-cell'; marketRowDescription = 'name'; marketStateColId = 'data'; + openMarketMenu = 'arrow-down'; validateMarketsAreDisplayed() { // We need this to ensure that ag-grid is fully rendered before asserting @@ -27,16 +28,12 @@ export default class MarketPage extends BasePage { 'Description', ]; - cy.get(this.marketRowHeaderClassname) - .each(($marketHeader, index) => { - cy.wrap($marketHeader).should( - 'have.text', - expectedMarketHeaders[index] - ); - }) - .then(($list) => { - cy.wrap($list).should('have.length', expectedMarketHeaders.length); - }); + for (let index = 0; index < expectedMarketHeaders.length; index++) { + cy.get(this.marketRowHeaderClassname).should( + 'contain.text', + expectedMarketHeaders[index] + ); + } cy.get(`[col-id='${this.marketRowNameColumn}']`).each(($marketName) => { cy.wrap($marketName).should('not.be.empty'); @@ -65,4 +62,8 @@ export default class MarketPage extends BasePage { 'portfolio=orders&trade=orderbook' ); } + + clickOpenMarketMenu() { + cy.getByTestId(this.openMarketMenu).click(); + } } diff --git a/apps/trading-e2e/src/support/step_definitions/common.step.ts b/apps/trading-e2e/src/support/step_definitions/common.step.ts index 132a6fa2d..5cf95f90f 100644 --- a/apps/trading-e2e/src/support/step_definitions/common.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/common.step.ts @@ -2,8 +2,10 @@ import { Given } from 'cypress-cucumber-preprocessor/steps'; import { hasOperationName } from '..'; import { generateMarketList } from '../mocks/generate-market-list'; import BasePage from '../pages/base-page'; +import MarketPage from '../pages/markets-page'; const basePage = new BasePage(); +const marketPage = new MarketPage(); Given('I am on the homepage', () => { cy.mockGQL('MarketsList', (req) => { @@ -15,4 +17,5 @@ Given('I am on the homepage', () => { }); cy.visit('/'); basePage.closeDialog(); + marketPage.validateMarketsAreDisplayed(); }); diff --git a/apps/trading-e2e/src/support/step_definitions/deposits.step.ts b/apps/trading-e2e/src/support/step_definitions/deposits.step.ts index dc47c6e47..025264a13 100644 --- a/apps/trading-e2e/src/support/step_definitions/deposits.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/deposits.step.ts @@ -30,7 +30,7 @@ Then('I can see the deposit form', () => { }); When('I submit a deposit with empty fields', () => { - depositsPage.updateTransactionform(); + depositsPage.updateTransactionForm(); depositsPage.submitForm(); }); @@ -39,7 +39,7 @@ Then('I can see empty form validation errors present', () => { }); Then('I enter the following deposit details in deposit form', (table) => { - depositsPage.updateTransactionform({ + depositsPage.updateTransactionForm({ asset: table.rowsHash().asset, to: Cypress.env(table.rowsHash().to), amount: table.rowsHash().amount, @@ -59,7 +59,7 @@ Then('Amount too small message shown', () => { }); And('I enter a valid amount', () => { - depositsPage.updateTransactionform({ amount: '1' }); + depositsPage.updateTransactionForm({ amount: '1' }); }); Then('Not approved message shown', () => { diff --git a/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts b/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts index 73d3952d2..a9722a928 100644 --- a/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/markets-page.step.ts @@ -19,6 +19,7 @@ const mockMarkets = () => { Then('I navigate to markets page', () => { mockMarkets(); marketsPage.navigateToMarkets(); + marketsPage.clickOpenMarketMenu(); cy.wait('@Markets'); }); diff --git a/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts b/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts index 13116f1a3..c082115ad 100644 --- a/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/trading-page.step.ts @@ -88,7 +88,7 @@ Given('I am on the trading page for an active market', () => { cy.visit('/markets/market-id'); cy.wait('@Market'); - cy.contains('Market: ACTIVE MARKET'); + cy.contains('ACTIVE MARKET'); }); Given('I am on the trading page for a suspended market', () => { @@ -96,7 +96,7 @@ Given('I am on the trading page for a suspended market', () => { cy.visit('/markets/market-id'); cy.wait('@Market'); - cy.contains('Market: SUSPENDED MARKET'); + cy.contains('SUSPENDED MARKET'); }); When('I click on {string} mocked market', (marketType) => { @@ -115,11 +115,11 @@ Then('trading page for {string} market is displayed', (marketType) => { switch (marketType) { case 'active': cy.wait('@Market'); - cy.contains('Market: ACTIVE MARKET'); + cy.contains('ACTIVE MARKET'); break; case 'suspended': cy.wait('@Market'); - cy.contains('Market: SUSPENDED MARKET'); + cy.contains('SUSPENDED MARKET'); break; } tradingPage.clickOnTradesTab(); diff --git a/apps/trading-e2e/src/support/step_definitions/vega-wallet.step.ts b/apps/trading-e2e/src/support/step_definitions/vega-wallet.step.ts index 9c4026a1c..52590e636 100644 --- a/apps/trading-e2e/src/support/step_definitions/vega-wallet.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/vega-wallet.step.ts @@ -27,6 +27,7 @@ When('I connect to Vega Wallet', () => { Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE') ); vegaWallet.clickConnectVegaWallet(); + vegaWallet.validateWalletConnected(); }); When('I open wallet dialog', () => { diff --git a/apps/trading-e2e/src/support/step_definitions/withdrawals.step.ts b/apps/trading-e2e/src/support/step_definitions/withdrawals.step.ts index e02f7bb43..169f8fb1a 100644 --- a/apps/trading-e2e/src/support/step_definitions/withdrawals.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/withdrawals.step.ts @@ -1,13 +1,16 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'; +import MarketPage from '../pages/markets-page'; import PortfolioPage from '../pages/portfolio-page'; import WithdrawalsPage from '../pages/withdrawals-page'; +const marketPage = new MarketPage(); const portfolioPage = new PortfolioPage(); const withdrawalsPage = new WithdrawalsPage(); Given('I navigate to withdrawal page', () => { cy.visit('/'); portfolioPage.closeDialog(); + marketPage.validateMarketsAreDisplayed(); portfolioPage.navigateToPortfolio(); portfolioPage.navigateToWithdraw(); }); @@ -26,14 +29,14 @@ When('click submit', () => { }); When('I enter an invalid ethereum address', () => { - withdrawalsPage.updateTransactionform({ + withdrawalsPage.updateTransactionForm({ to: '0x0dAAACaa868f87BB4666F918742141cAEAe893Fa', }); withdrawalsPage.clickSubmit(); }); When('I select {string}', (selectedAsset) => { - withdrawalsPage.updateTransactionform({ + withdrawalsPage.updateTransactionForm({ asset: selectedAsset, }); }); @@ -47,7 +50,7 @@ When('I click Use maximum', () => { }); When('I enter the following details in withdrawal form', (table) => { - withdrawalsPage.updateTransactionform({ + withdrawalsPage.updateTransactionForm({ asset: table.rowsHash().asset, to: table.rowsHash().to, amount: table.rowsHash().amount, @@ -56,7 +59,7 @@ When('I enter the following details in withdrawal form', (table) => { }); When('I succesfully fill in and submit withdrawal form', () => { - withdrawalsPage.updateTransactionform({ + withdrawalsPage.updateTransactionForm({ asset: Cypress.env('WITHDRAWAL_ASSET_ID'), amount: '0.1', }); diff --git a/apps/trading-e2e/src/support/trading-windows/deal-ticket.ts b/apps/trading-e2e/src/support/trading-windows/deal-ticket.ts index 8ba4c6c74..0821ad813 100644 --- a/apps/trading-e2e/src/support/trading-windows/deal-ticket.ts +++ b/apps/trading-e2e/src/support/trading-windows/deal-ticket.ts @@ -20,11 +20,11 @@ export default class DealTicket { ); if (isBuy == false) { - cy.getByTestId(this.sellOrder).click(); + cy.getByTestId(this.sellOrder)?.click(); } - cy.getByTestId(this.orderSizeField).clear().type(orderSize); - cy.getByTestId(this.orderTypeDropDown).select(orderType); + cy.getByTestId(this.orderSizeField)?.clear().type(orderSize); + cy.getByTestId(this.orderTypeDropDown)?.select(orderType); } placeLimitOrder( @@ -33,10 +33,10 @@ export default class DealTicket { orderPrice: string, orderType: string ) { - cy.getByTestId(this.limitOrderType).click(); + cy.getByTestId(this.limitOrderType)?.click(); if (isBuy == false) { - cy.getByTestId(this.sellOrder).click(); + cy.getByTestId(this.sellOrder)?.click(); } cy.getByTestId(this.orderSizeField).clear().type(orderSize); diff --git a/apps/trading-e2e/src/support/vega-wallet/index.ts b/apps/trading-e2e/src/support/vega-wallet/index.ts index bdf88966b..ce921871d 100644 --- a/apps/trading-e2e/src/support/vega-wallet/index.ts +++ b/apps/trading-e2e/src/support/vega-wallet/index.ts @@ -50,6 +50,10 @@ export default class VegaWallet { ); } + validateWalletConnected() { + cy.getByTestId(this.connectVegaBtn).should('contain.text', '…'); + } + selectPublicKey() { cy.getByTestId(this.selectPublicKeyBtn).click(); } diff --git a/apps/trading/components/navbar/navbar.tsx b/apps/trading/components/navbar/navbar.tsx index 51df59f8d..236ef8a0a 100644 --- a/apps/trading/components/navbar/navbar.tsx +++ b/apps/trading/components/navbar/navbar.tsx @@ -2,9 +2,38 @@ import { useRouter } from 'next/router'; import { Vega } from '../icons/vega'; import Link from 'next/link'; import { AnchorButton } from '@vegaprotocol/ui-toolkit'; -import { t } from '@vegaprotocol/react-helpers'; +import { LocalStorage, t } from '@vegaprotocol/react-helpers'; +import { useEffect, useState } from 'react'; export const Navbar = () => { + const initNavItemsState = [ + { + name: t('Portfolio'), + path: '/portfolio', + testId: 'portfolio-link', + slug: '', + }, + ]; + const [navItems, setNavItems] = useState(initNavItemsState); + const marketId = LocalStorage.getItem('marketId') ?? ''; + + useEffect(() => { + setNavItems([ + { + name: t('Trading'), + path: '/markets', + testId: 'markets-link', + slug: marketId, + }, + { + name: t('Portfolio'), + path: '/portfolio', + testId: 'portfolio-link', + slug: '', + }, + ]); + }, [marketId]); + return ( @@ -26,20 +52,30 @@ interface NavLinkProps { name: string; path: string; exact?: boolean; + testId?: string; + slug?: string; } -const NavLink = ({ name, path, exact }: NavLinkProps) => { +const NavLink = ({ + name, + path, + exact, + testId = name, + slug = '', +}: NavLinkProps) => { const router = useRouter(); const isActive = router.asPath === path || (!exact && router.asPath.startsWith(path)); + const href = slug !== '' ? `${path}/${slug}` : path; return ( { e.preventDefault(); - router.push(path); + router.push(href); }} > {name} diff --git a/apps/trading/pages/index.page.tsx b/apps/trading/pages/index.page.tsx index ee52b352b..0f36de7b2 100644 --- a/apps/trading/pages/index.page.tsx +++ b/apps/trading/pages/index.page.tsx @@ -1,4 +1,5 @@ import { gql, useQuery } from '@apollo/client'; +import { LocalStorage } from '@vegaprotocol/react-helpers'; import { MarketTradingMode } from '@vegaprotocol/types'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import sortBy from 'lodash/sortBy'; @@ -35,10 +36,13 @@ export function Index() { // should be the oldest market that is currently trading in continuous mode(i.e. not in auction). const { data, error, loading } = useQuery(MARKETS_QUERY); const setLandingDialog = useGlobalStore((state) => state.setLandingDialog); + const lastSelectedMarketId = LocalStorage.getItem('marketId'); useEffect(() => { if (data) { - const marketId = marketList(data)[0]?.id; + const marketId = lastSelectedMarketId + ? lastSelectedMarketId + : marketList(data)[0]?.id; // If a default market is found, go to it with the landing dialog open if (marketId) { @@ -50,7 +54,7 @@ export function Index() { replace('/markets'); } } - }, [data, replace, setLandingDialog]); + }, [data, lastSelectedMarketId, replace, setLandingDialog]); return ( diff --git a/apps/trading/pages/markets/[marketId].page.tsx b/apps/trading/pages/markets/[marketId].page.tsx index 9231e6958..9fbdc0315 100644 --- a/apps/trading/pages/markets/[marketId].page.tsx +++ b/apps/trading/pages/markets/[marketId].page.tsx @@ -1,21 +1,55 @@ import { gql } from '@apollo/client'; -import type { Market, MarketVariables } from './__generated__/Market'; import { Splash } from '@vegaprotocol/ui-toolkit'; import { useRouter } from 'next/router'; import React, { useEffect, useState } from 'react'; import debounce from 'lodash/debounce'; import { PageQueryContainer } from '../../components/page-query-container'; import { TradeGrid, TradePanels } from './trade-grid'; -import { t } from '@vegaprotocol/react-helpers'; +import { LocalStorage, t } from '@vegaprotocol/react-helpers'; import { useGlobalStore } from '../../stores'; import { LandingDialog } from '@vegaprotocol/market-list'; +import type { Market, MarketVariables } from './__generated__/Market'; +import { Interval } from '@vegaprotocol/types'; // Top level page query const MARKET_QUERY = gql` - query Market($marketId: ID!) { + query Market($marketId: ID!, $interval: Interval!, $since: String!) { market(id: $marketId) { id name + tradingMode + state + decimalPlaces + data { + market { + id + } + markPrice + indicativeVolume + bestBidVolume + bestOfferVolume + bestStaticBidVolume + bestStaticOfferVolume + indicativeVolume + } + tradableInstrument { + instrument { + name + code + metadata { + tags + } + } + } + marketTimestamps { + open + close + } + candles(interval: $interval, since: $since) { + open + close + volume + } } } `; @@ -29,6 +63,9 @@ const MarketPage = ({ id }: { id?: string }) => { const marketId = id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId); + const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600; + const yTimestamp = new Date(yesterday * 1000).toISOString(); + if (!marketId) { return ( @@ -37,12 +74,15 @@ const MarketPage = ({ id }: { id?: string }) => { ); } + LocalStorage.setItem('marketId', marketId); return ( query={MARKET_QUERY} options={{ variables: { marketId, + interval: Interval.I1H, + since: yTimestamp, }, fetchPolicy: 'network-only', }} diff --git a/apps/trading/pages/markets/__generated__/Market.ts b/apps/trading/pages/markets/__generated__/Market.ts index cc812fc8c..683136706 100644 --- a/apps/trading/pages/markets/__generated__/Market.ts +++ b/apps/trading/pages/markets/__generated__/Market.ts @@ -3,10 +3,112 @@ // @generated // This file was automatically generated and should not be edited. +import { Interval, MarketTradingMode, MarketState } from "@vegaprotocol/types"; + // ==================================================== // GraphQL query operation: Market // ==================================================== +export interface Market_market_data_market { + __typename: "Market"; + /** + * Market ID + */ + id: string; +} + +export interface Market_market_data { + __typename: "MarketData"; + /** + * market id of the associated mark price + */ + market: Market_market_data_market; + /** + * the mark price (actually an unsigned int) + */ + markPrice: string; + /** + * indicative volume if the auction ended now, 0 if not in auction mode + */ + indicativeVolume: string; + /** + * the aggregated volume being bid at the best bid price. + */ + bestBidVolume: string; + /** + * the aggregated volume being offered at the best offer price. + */ + bestOfferVolume: string; + /** + * the aggregated volume being offered at the best static bid price, excluding pegged orders + */ + bestStaticBidVolume: string; + /** + * the aggregated volume being offered at the best static offer price, excluding pegged orders. + */ + bestStaticOfferVolume: string; +} + +export interface Market_market_tradableInstrument_instrument_metadata { + __typename: "InstrumentMetadata"; + /** + * An arbitrary list of tags to associated to associate to the Instrument (string list) + */ + tags: string[] | null; +} + +export interface Market_market_tradableInstrument_instrument { + __typename: "Instrument"; + /** + * Full and fairly descriptive name for the instrument + */ + name: string; + /** + * A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string) + */ + code: string; + /** + * Metadata for this instrument + */ + metadata: Market_market_tradableInstrument_instrument_metadata; +} + +export interface Market_market_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of or reference to a fully specified instrument. + */ + instrument: Market_market_tradableInstrument_instrument; +} + +export interface Market_market_marketTimestamps { + __typename: "MarketTimestamps"; + /** + * Time when the market is open and ready to accept trades + */ + open: string | null; + /** + * Time when the market is closed + */ + close: string | null; +} + +export interface Market_market_candles { + __typename: "Candle"; + /** + * Open price (uint64) + */ + open: string; + /** + * Close price (uint64) + */ + close: string; + /** + * Volume price (uint64) + */ + volume: string; +} + export interface Market_market { __typename: "Market"; /** @@ -17,6 +119,47 @@ export interface Market_market { * Market full name */ name: string; + /** + * Current mode of execution of the market + */ + tradingMode: MarketTradingMode; + /** + * Current state of the market + */ + state: MarketState; + /** + * decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct + * number denominated in the currency of the Market. (uint64) + * + * Examples: + * Currency Balance decimalPlaces Real Balance + * GBP 100 0 GBP 100 + * GBP 100 2 GBP 1.00 + * GBP 100 4 GBP 0.01 + * GBP 1 4 GBP 0.0001 ( 0.01p ) + * + * GBX (pence) 100 0 GBP 1.00 (100p ) + * GBX (pence) 100 2 GBP 0.01 ( 1p ) + * GBX (pence) 100 4 GBP 0.0001 ( 0.01p ) + * GBX (pence) 1 4 GBP 0.000001 ( 0.0001p) + */ + decimalPlaces: number; + /** + * marketData for the given market + */ + data: Market_market_data | null; + /** + * An instance of or reference to a tradable instrument. + */ + tradableInstrument: Market_market_tradableInstrument; + /** + * timestamps for state changes in the market + */ + marketTimestamps: Market_market_marketTimestamps; + /** + * Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by params + */ + candles: (Market_market_candles | null)[] | null; } export interface Market { @@ -28,4 +171,6 @@ export interface Market { export interface MarketVariables { marketId: string; + interval: Interval; + since: string; } diff --git a/apps/trading/pages/markets/index.page.tsx b/apps/trading/pages/markets/index.page.tsx index 892cf76e0..67bb8bcbf 100644 --- a/apps/trading/pages/markets/index.page.tsx +++ b/apps/trading/pages/markets/index.page.tsx @@ -1,8 +1,6 @@ import { MarketsContainer } from '@vegaprotocol/market-list'; -const Markets = () => { - return ; -}; +const Markets = () => ; Markets.getInitialProps = () => ({ page: 'markets', diff --git a/apps/trading/pages/markets/trade-grid.tsx b/apps/trading/pages/markets/trade-grid.tsx index 8a80d5afd..7e6eb7fed 100644 --- a/apps/trading/pages/markets/trade-grid.tsx +++ b/apps/trading/pages/markets/trade-grid.tsx @@ -13,6 +13,9 @@ import { t } from '@vegaprotocol/react-helpers'; import { AccountsContainer } from '@vegaprotocol/accounts'; import { DepthChartContainer } from '@vegaprotocol/market-depth'; import { CandlesChartContainer } from '@vegaprotocol/candles-chart'; +import { SelectMarketDialog } from '@vegaprotocol/market-list'; +import { ArrowDown, PriceCellChange } from '@vegaprotocol/ui-toolkit'; +import type { CandleClose } from '@vegaprotocol/types'; const TradingViews = { Candles: CandlesChartContainer, @@ -31,6 +34,58 @@ interface TradeGridProps { market: Market_market; } +export const TradeMarketHeader = ({ market }: TradeGridProps) => { + const [open, setOpen] = useState(false); + const candlesClose: string[] = (market?.candles || []) + .map((candle) => candle?.close) + .filter((c): c is CandleClose => c !== null); + const headerItemClassName = 'whitespace-nowrap flex flex-col'; + const itemClassName = + 'font-sans font-normal mb-0 text-dark/80 dark:text-white/80 text-ui-small'; + const itemValueClassName = + 'capitalize font-sans tracking-tighter text-black dark:text-white text-ui'; + return ( +
+ +
+ + +
+
+ Change (24h) + +
+
+ Volume + + {market.data && market.data.indicativeVolume !== '0' + ? market.data.indicativeVolume + : '-'} + +
+
+ Trading mode + {market.tradingMode} +
+
+ State + {market.state} +
+
+
+
+ ); +}; + export const TradeGrid = ({ market }: TradeGridProps) => { const wrapperClasses = classNames( 'h-full max-h-full', @@ -38,50 +93,49 @@ export const TradeGrid = ({ market }: TradeGridProps) => { 'bg-black-10 dark:bg-white-10', 'text-ui' ); + return ( -
-
-

- {t('Market')}: {market.name} -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ <> + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); }; @@ -124,11 +178,7 @@ export const TradePanels = ({ market }: TradePanelsProps) => { return (
-
-

- {t('Market')}: {market.name} -

-
+
{({ width, height }) => ( diff --git a/libs/market-list/src/lib/components/landing/index.ts b/libs/market-list/src/lib/components/landing/index.ts index 1acd77dfa..8fbbbc4d4 100644 --- a/libs/market-list/src/lib/components/landing/index.ts +++ b/libs/market-list/src/lib/components/landing/index.ts @@ -1,2 +1,3 @@ export * from './landing-dialog'; +export * from './select-market-dialog'; export * from './select-market-list'; diff --git a/libs/market-list/src/lib/components/landing/select-market-dialog.spec.tsx b/libs/market-list/src/lib/components/landing/select-market-dialog.spec.tsx new file mode 100644 index 000000000..4e0ff4181 --- /dev/null +++ b/libs/market-list/src/lib/components/landing/select-market-dialog.spec.tsx @@ -0,0 +1,33 @@ +import { render, screen } from '@testing-library/react'; +import type { ReactNode } from 'react'; +import { SelectMarketDialog } from './select-market-dialog'; +import { MockedProvider } from '@apollo/client/testing'; + +jest.mock( + 'next/link', + () => + ({ children }: { children: ReactNode }) => + children +); + +jest.mock('next/router', () => ({ + useRouter() { + return { + route: '/', + pathname: '', + query: '', + asPath: '', + }; + }, +})); + +describe('SelectMarketDialog', () => { + it('should render select a market dialog', () => { + render( + + jest.fn()} /> + + ); + expect(screen.getByText('Select a market')).toBeTruthy(); + }); +}); diff --git a/libs/market-list/src/lib/components/landing/select-market-dialog.tsx b/libs/market-list/src/lib/components/landing/select-market-dialog.tsx new file mode 100644 index 000000000..41e2cb24b --- /dev/null +++ b/libs/market-list/src/lib/components/landing/select-market-dialog.tsx @@ -0,0 +1,28 @@ +import { Dialog, Intent } from '@vegaprotocol/ui-toolkit'; +import { t } from '@vegaprotocol/react-helpers'; +import { MarketsContainer } from '../markets-container'; + +export interface SelectMarketListProps { + dialogOpen: boolean; + setDialogOpen: (open: boolean) => void; +} + +export const SelectMarketDialog = ({ + dialogOpen, + setDialogOpen, +}: SelectMarketListProps) => { + return ( + setDialogOpen(false)} + titleClassNames="font-bold font-sans text-3xl tracking-tight mb-0 pl-8" + contentClassNames="w-full md:w-[1120px]" + > +
+ +
+
+ ); +}; diff --git a/libs/market-list/src/lib/components/landing/select-market-list.tsx b/libs/market-list/src/lib/components/landing/select-market-list.tsx index 61b74060b..0f62f6b72 100644 --- a/libs/market-list/src/lib/components/landing/select-market-list.tsx +++ b/libs/market-list/src/lib/components/landing/select-market-list.tsx @@ -3,19 +3,21 @@ import { PriceCell, t, } from '@vegaprotocol/react-helpers'; +import type { CandleClose } from '@vegaprotocol/types'; import { PriceCellChange, Sparkline } from '@vegaprotocol/ui-toolkit'; import Link from 'next/link'; import { mapDataToMarketList } from '../../utils'; import type { MarketList } from '../markets-container/__generated__/MarketList'; -export interface SelectMarketListProps { +export interface SelectMarketListDataProps { data: MarketList | undefined; onSelect: (id: string) => void; } -type CandleClose = Required; - -export const SelectMarketList = ({ data, onSelect }: SelectMarketListProps) => { +export const SelectMarketList = ({ + data, + onSelect, +}: SelectMarketListDataProps) => { const thClassNames = (direction: 'left' | 'right') => `px-8 text-${direction} font-sans font-normal text-ui-small leading-9 mb-0 text-dark/80 dark:text-white/80`; const tdClassNames = diff --git a/libs/market-list/src/lib/components/markets-container/markets-container.tsx b/libs/market-list/src/lib/components/markets-container/markets-container.tsx index 59064ade3..a80afa2d7 100644 --- a/libs/market-list/src/lib/components/markets-container/markets-container.tsx +++ b/libs/market-list/src/lib/components/markets-container/markets-container.tsx @@ -13,7 +13,7 @@ import type { import { marketsDataProvider } from './markets-data-provider'; export const MarketsContainer = () => { - const { pathname, push } = useRouter(); + const { push } = useRouter(); const gridRef = useRef(null); const update = useCallback( (delta: Markets_markets_data) => { @@ -57,7 +57,7 @@ export const MarketsContainer = () => { ref={gridRef} data={data} onRowClicked={(id) => - push(`${pathname}/${id}?portfolio=orders&trade=orderbook`) + push(`/markets/${id}?portfolio=orders&trade=orderbook`) } /> diff --git a/libs/positions/src/lib/positions-table.spec.tsx b/libs/positions/src/lib/positions-table.spec.tsx index 70b84e231..7f5326399 100644 --- a/libs/positions/src/lib/positions-table.spec.tsx +++ b/libs/positions/src/lib/positions-table.spec.tsx @@ -4,7 +4,7 @@ import type { Positions_party_positions } from './__generated__/Positions'; import { MarketTradingMode } from '@vegaprotocol/types'; const singleRow: Positions_party_positions = { - realisedPNL: '5', + realisedPNL: '520000000', openVolume: '100', unrealisedPNL: '895000', averageEntryPrice: '1129935', @@ -93,7 +93,7 @@ it('Correct formatting applied', async () => { '+100', '11.29935', '11.38885', - '+5', + '+5,200.000', ]; cells.forEach((cell, i) => { expect(cell).toHaveTextContent(expectedValues[i]); diff --git a/libs/positions/src/lib/positions-table.tsx b/libs/positions/src/lib/positions-table.tsx index c5a420920..e70c01e1e 100644 --- a/libs/positions/src/lib/positions-table.tsx +++ b/libs/positions/src/lib/positions-table.tsx @@ -131,8 +131,10 @@ export const PositionsTable = forwardRef( 'color-vega-red': ({ value }: { value: string }) => Number(value) < 0, }} - valueFormatter={({ value }: ValueFormatterParams) => - volumePrefix(value) + valueFormatter={({ value, data }: ValueFormatterParams) => + volumePrefix( + addDecimalsFormatNumber(value, data.market.decimalPlaces, 3) + ) } cellRenderer="PriceFlashCell" /> diff --git a/libs/react-helpers/src/lib/generic-data-provider.ts b/libs/react-helpers/src/lib/generic-data-provider.ts index f211c24fe..70d44c8eb 100644 --- a/libs/react-helpers/src/lib/generic-data-provider.ts +++ b/libs/react-helpers/src/lib/generic-data-provider.ts @@ -46,8 +46,8 @@ interface GetDelta { } /** - * @param subscriptionQuery query that will beused for subscription - * @param update function that will be execued on each onNext, it should update data base on delta, it can restart data provider + * @param subscriptionQuery query that will be used for subscription + * @param update function that will be executed on each onNext, it should update data base on delta, it can restart data provider * @param getData transforms received query data to format that will be stored in data provider * @param getDelta transforms delta data to format that will be stored in data provider * @param fetchPolicy @@ -63,7 +63,7 @@ function makeDataProviderInternal( ): Subscribe { // list of callbacks passed through subscribe call const callbacks: UpdateCallback[] = []; - // subscription is started before inital query, all deltas that will arrive before inital query response are put on queue + // subscription is started before initial query, all deltas that will arrive before initial query response are put on queue const updateQueue: Delta[] = []; let variables: OperationVariables | undefined = undefined; @@ -88,7 +88,7 @@ function makeDataProviderInternal( callbacks.forEach((callback) => notify(callback, delta)); }; - const initalFetch = async () => { + const initialFetch = async () => { if (!client) { return; } @@ -99,7 +99,7 @@ function makeDataProviderInternal( fetchPolicy, }); data = getData(res.data); - // if there was some updates received from subscription during initial query loading apply them on just reveived data + // if there was some updates received from subscription during initial query loading apply them on just received data if (data && updateQueue && updateQueue.length > 0) { data = produce(data, (draft) => { while (updateQueue.length) { @@ -135,7 +135,7 @@ function makeDataProviderInternal( } else { loading = true; error = undefined; - initalFetch(); + initialFetch(); } }; @@ -176,7 +176,7 @@ function makeDataProviderInternal( }, () => restart() ); - await initalFetch(); + await initialFetch(); }; const reset = () => { @@ -242,8 +242,8 @@ const memoize = ( /** * @param query Query - * @param subscriptionQuery Query query that will beused for subscription - * @param update Update function that will be execued on each onNext, it should update data base on delta, it can restart data provider + * @param subscriptionQuery Query query that will be used for subscription + * @param update Update function that will be executed on each onNext, it should update data base on delta, it can restart data provider * @param getData transforms received query data to format that will be stored in data provider * @param getDelta transforms delta data to format that will be stored in data provider * @param fetchPolicy diff --git a/libs/react-helpers/src/lib/grid/price-cell.tsx b/libs/react-helpers/src/lib/grid/price-cell.tsx index f645fd88b..e8200de9c 100644 --- a/libs/react-helpers/src/lib/grid/price-cell.tsx +++ b/libs/react-helpers/src/lib/grid/price-cell.tsx @@ -13,7 +13,7 @@ export const PriceCell = React.memo( return -; } return ( - + {valueFormatted} ); diff --git a/libs/types/src/candle.ts b/libs/types/src/candle.ts new file mode 100644 index 000000000..d4d1291d9 --- /dev/null +++ b/libs/types/src/candle.ts @@ -0,0 +1 @@ +export type CandleClose = Required; diff --git a/libs/types/src/index.ts b/libs/types/src/index.ts index f2426ad97..b4f74515a 100644 --- a/libs/types/src/index.ts +++ b/libs/types/src/index.ts @@ -1 +1,2 @@ export * from './__generated__/globalTypes'; +export * from './candle'; diff --git a/libs/ui-toolkit/src/components/arrows/arrow.tsx b/libs/ui-toolkit/src/components/arrows/arrow.tsx index 744ec7ef2..550c00302 100644 --- a/libs/ui-toolkit/src/components/arrows/arrow.tsx +++ b/libs/ui-toolkit/src/components/arrows/arrow.tsx @@ -1,13 +1,38 @@ -export const ArrowUp = () => ( +export interface ArrowStyleProps { + color?: string; + borderX?: number; + borderTop?: number; + borderBottom?: number; +} + +export const ArrowUp = ({ + color = 'green', + borderX = 4, + borderBottom = 4, +}: ArrowStyleProps) => ( ); -export const ArrowDown = () => ( +export const ArrowDown = ({ + color = 'red', + borderX = 4, + borderTop = 4, +}: ArrowStyleProps) => ( ); diff --git a/libs/ui-toolkit/src/components/button/button.tsx b/libs/ui-toolkit/src/components/button/button.tsx index 53824534e..361157a7a 100644 --- a/libs/ui-toolkit/src/components/button/button.tsx +++ b/libs/ui-toolkit/src/components/button/button.tsx @@ -215,12 +215,12 @@ export const AnchorButton = forwardRef( className, prependIconName, appendIconName, - ...prosp + ...props }, ref ) => { return ( - + {getContent(children, prependIconName, appendIconName)} ); diff --git a/libs/ui-toolkit/src/components/dialog/dialog.tsx b/libs/ui-toolkit/src/components/dialog/dialog.tsx index f838f9136..9433d0de7 100644 --- a/libs/ui-toolkit/src/components/dialog/dialog.tsx +++ b/libs/ui-toolkit/src/components/dialog/dialog.tsx @@ -12,6 +12,7 @@ interface DialogProps { title?: string; intent?: Intent; titleClassNames?: string; + contentClassNames?: string; } export function Dialog({ @@ -21,13 +22,15 @@ export function Dialog({ title, intent, titleClassNames, + contentClassNames, }: DialogProps) { const contentClasses = classNames( // Positions the modal in the center of screen 'z-20 fixed w-full md:w-[520px] px-28 py-24 top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]', // Need to apply background and text colors again as content is rendered in a portal 'dark:bg-black dark:text-white-95 bg-white text-black-95', - getIntentShadow(intent) + getIntentShadow(intent), + contentClassNames ); return ( onChange(x)}>