c576037b58
* 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
565 lines
14 KiB
TypeScript
565 lines
14 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import { TradingModeTooltip } from '@vegaprotocol/deal-ticket';
|
|
import { FeesCell } from '@vegaprotocol/market-info';
|
|
import {
|
|
calcCandleHigh,
|
|
calcCandleLow,
|
|
calcCandleVolume,
|
|
} from '@vegaprotocol/market-list';
|
|
import {
|
|
addDecimalsNormalizeNumber,
|
|
PriceCell,
|
|
signedNumberCssClass,
|
|
t,
|
|
} from '@vegaprotocol/react-helpers';
|
|
import {
|
|
AuctionTrigger,
|
|
AuctionTriggerMapping,
|
|
MarketTradingMode,
|
|
MarketTradingModeMapping,
|
|
} from '@vegaprotocol/types';
|
|
import {
|
|
Link as UILink,
|
|
PriceCellChange,
|
|
Sparkline,
|
|
Tooltip,
|
|
} from '@vegaprotocol/ui-toolkit';
|
|
import isNil from 'lodash/isNil';
|
|
|
|
import type { CandleClose } from '@vegaprotocol/types';
|
|
import type {
|
|
MarketWithData,
|
|
MarketWithCandles,
|
|
} from '@vegaprotocol/market-list';
|
|
import { Link } from 'react-router-dom';
|
|
|
|
type Market = MarketWithData & MarketWithCandles;
|
|
|
|
export const cellClassNames = 'py-1 first:text-left text-right';
|
|
|
|
const TradingMode = ({ market }: { market: Market }) => {
|
|
return (
|
|
<Tooltip
|
|
description={
|
|
market && (
|
|
<TradingModeTooltip
|
|
tradingMode={market.tradingMode}
|
|
trigger={market.data?.trigger || null}
|
|
/>
|
|
)
|
|
}
|
|
>
|
|
<span>
|
|
{market.tradingMode ===
|
|
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
|
market.data?.trigger &&
|
|
market.data.trigger !== AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
|
? `${MarketTradingModeMapping[market.tradingMode]}
|
|
- ${AuctionTriggerMapping[market.data.trigger]}`
|
|
: MarketTradingModeMapping[market.tradingMode]}
|
|
</span>
|
|
</Tooltip>
|
|
);
|
|
};
|
|
|
|
const FeesInfo = () => {
|
|
return (
|
|
<Tooltip
|
|
description={
|
|
<span>
|
|
{t(
|
|
'Fees are paid by market takers on aggressive orders only. The fee displayed is made up of:'
|
|
)}
|
|
<ul className="list-disc ml-4">
|
|
<li>{t('An infrastructure fee')}</li>
|
|
<li>{t('A liquidity provision fee')}</li>
|
|
<li>{t('A maker fee')}</li>
|
|
</ul>
|
|
</span>
|
|
}
|
|
>
|
|
<span>{t('Taker fee')}</span>
|
|
</Tooltip>
|
|
);
|
|
};
|
|
|
|
export enum ColumnKind {
|
|
Market,
|
|
LastPrice,
|
|
Change24,
|
|
Asset,
|
|
ProductType,
|
|
Sparkline,
|
|
High24,
|
|
Low24,
|
|
TradingMode,
|
|
Volume,
|
|
Fee,
|
|
Position,
|
|
FullName,
|
|
}
|
|
|
|
export interface Column {
|
|
kind: ColumnKind;
|
|
value: string | React.ReactNode;
|
|
className: string;
|
|
onlyOnDetailed: boolean;
|
|
dataTestId?: string;
|
|
}
|
|
|
|
const headers: Column[] = [
|
|
{
|
|
kind: ColumnKind.Market,
|
|
value: t('Market'),
|
|
className: cellClassNames,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.ProductType,
|
|
value: t('Type'),
|
|
className: `py-2 text-left hidden sm:table-cell`,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.LastPrice,
|
|
value: t('Last price'),
|
|
className: cellClassNames,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.Change24,
|
|
value: t('Change (24h)'),
|
|
className: cellClassNames,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.Sparkline,
|
|
value: t(''),
|
|
className: `${cellClassNames} hidden lg:table-cell`,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.Asset,
|
|
value: t('Settlement asset'),
|
|
className: `${cellClassNames} hidden sm:table-cell`,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.High24,
|
|
value: t('24h High'),
|
|
className: `${cellClassNames} hidden xl:table-cell`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
{
|
|
kind: ColumnKind.Low24,
|
|
value: t('24h Low'),
|
|
className: `${cellClassNames} hidden xl:table-cell`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
{
|
|
kind: ColumnKind.Volume,
|
|
value: t('24h Volume'),
|
|
className: `${cellClassNames} hidden lg:table-cell`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
{
|
|
kind: ColumnKind.TradingMode,
|
|
value: t('Trading mode'),
|
|
className: `${cellClassNames} hidden lg:table-cell`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
{
|
|
kind: ColumnKind.Fee,
|
|
value: <FeesInfo />,
|
|
className: `${cellClassNames} hidden xl:table-cell`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
];
|
|
|
|
export const columnHeadersPositionMarkets: Column[] = [
|
|
...headers,
|
|
{
|
|
kind: ColumnKind.Position,
|
|
value: t('Position'),
|
|
className: `${cellClassNames} hidden xxl:table-cell`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
];
|
|
|
|
export const columnHeaders: Column[] = [
|
|
...headers,
|
|
{
|
|
kind: ColumnKind.FullName,
|
|
value: t('Full name'),
|
|
className: `${cellClassNames} hidden xxl:block`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
];
|
|
|
|
export type OnCellClickHandler = (
|
|
e: React.MouseEvent,
|
|
kind: ColumnKind,
|
|
value: string
|
|
) => void;
|
|
|
|
export const columns = (
|
|
market: Market,
|
|
onSelect: (id: string) => void,
|
|
onCellClick: OnCellClickHandler
|
|
) => {
|
|
const candlesClose = market.candles
|
|
?.map((candle) => candle?.close)
|
|
.filter((c: string | undefined): c is CandleClose => !isNil(c));
|
|
const candleLow = market.candles && calcCandleLow(market.candles);
|
|
const candleHigh = market.candles && calcCandleHigh(market.candles);
|
|
const candleVolume = market.candles && calcCandleVolume(market.candles);
|
|
const handleKeyPress = (
|
|
event: React.KeyboardEvent<HTMLAnchorElement>,
|
|
id: string
|
|
) => {
|
|
if (event.key === 'Enter' && onSelect) {
|
|
return onSelect(id);
|
|
}
|
|
};
|
|
const selectMarketColumns: Column[] = [
|
|
{
|
|
kind: ColumnKind.Market,
|
|
value: (
|
|
<Link
|
|
to={`/markets/${market.id}`}
|
|
data-testid={`market-link-${market.id}`}
|
|
onKeyPress={(event) => handleKeyPress(event, market.id)}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
onSelect(market.id);
|
|
}}
|
|
>
|
|
<UILink>{market.tradableInstrument.instrument.code}</UILink>
|
|
</Link>
|
|
),
|
|
className: cellClassNames,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.ProductType,
|
|
value: market.tradableInstrument.instrument.product.__typename,
|
|
className: `py-2 text-left hidden sm:table-cell`,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.LastPrice,
|
|
value: market.data?.markPrice ? (
|
|
<PriceCell
|
|
value={Number(market.data?.markPrice)}
|
|
valueFormatted={addDecimalsNormalizeNumber(
|
|
market.data?.markPrice.toString(),
|
|
market.decimalPlaces,
|
|
2
|
|
)}
|
|
/>
|
|
) : (
|
|
'-'
|
|
),
|
|
className: cellClassNames,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.Change24,
|
|
value: candlesClose && (
|
|
<PriceCellChange
|
|
candles={candlesClose}
|
|
decimalPlaces={market.decimalPlaces}
|
|
/>
|
|
),
|
|
className: cellClassNames,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.Sparkline,
|
|
value: market.candles && (
|
|
<Sparkline
|
|
width={100}
|
|
height={20}
|
|
muted={false}
|
|
data={candlesClose?.map((c: string) => Number(c)) || []}
|
|
/>
|
|
),
|
|
className: `${cellClassNames} hidden lg:table-cell`,
|
|
onlyOnDetailed: false && candlesClose,
|
|
},
|
|
{
|
|
kind: ColumnKind.Asset,
|
|
value: (
|
|
<button
|
|
data-dialog-trigger
|
|
className="inline underline"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onCellClick(
|
|
e,
|
|
ColumnKind.Asset,
|
|
market.tradableInstrument.instrument.product.settlementAsset
|
|
.symbol
|
|
);
|
|
}}
|
|
>
|
|
{market.tradableInstrument.instrument.product.settlementAsset.symbol}
|
|
</button>
|
|
),
|
|
dataTestId: 'settlement-asset',
|
|
className: `${cellClassNames} hidden sm:table-cell`,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.High24,
|
|
value: candleHigh ? (
|
|
<PriceCell
|
|
value={Number(candleHigh)}
|
|
valueFormatted={addDecimalsNormalizeNumber(
|
|
candleHigh.toString(),
|
|
market.decimalPlaces,
|
|
2
|
|
)}
|
|
/>
|
|
) : (
|
|
'-'
|
|
),
|
|
className: `${cellClassNames} hidden xl:table-cell font-mono`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
{
|
|
kind: ColumnKind.Low24,
|
|
value: candleLow ? (
|
|
<PriceCell
|
|
value={Number(candleLow)}
|
|
valueFormatted={addDecimalsNormalizeNumber(
|
|
candleLow.toString(),
|
|
market.decimalPlaces,
|
|
2
|
|
)}
|
|
/>
|
|
) : (
|
|
'-'
|
|
),
|
|
className: `${cellClassNames} hidden xl:table-cell font-mono`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
{
|
|
kind: ColumnKind.Volume,
|
|
value: candleVolume
|
|
? addDecimalsNormalizeNumber(
|
|
candleVolume.toString(),
|
|
market.positionDecimalPlaces,
|
|
2
|
|
)
|
|
: '-',
|
|
className: `${cellClassNames} hidden lg:table-cell font-mono`,
|
|
onlyOnDetailed: true,
|
|
dataTestId: 'market-volume',
|
|
},
|
|
{
|
|
kind: ColumnKind.TradingMode,
|
|
value: <TradingMode market={market} />,
|
|
className: `${cellClassNames} hidden lg:table-cell`,
|
|
onlyOnDetailed: true,
|
|
dataTestId: 'trading-mode-col',
|
|
},
|
|
{
|
|
kind: ColumnKind.Fee,
|
|
value: <FeesCell feeFactors={market.fees.factors} />,
|
|
className: `${cellClassNames} hidden xl:table-cell font-mono`,
|
|
onlyOnDetailed: true,
|
|
dataTestId: 'taker-fee',
|
|
},
|
|
{
|
|
kind: ColumnKind.FullName,
|
|
value: market.tradableInstrument.instrument.name,
|
|
className: `${cellClassNames} hidden xxl:block`,
|
|
onlyOnDetailed: true,
|
|
dataTestId: 'market-name',
|
|
},
|
|
];
|
|
return selectMarketColumns;
|
|
};
|
|
|
|
export const columnsPositionMarkets = (
|
|
market: Market,
|
|
onSelect: (id: string) => void,
|
|
openVolume?: string,
|
|
onCellClick?: OnCellClickHandler
|
|
) => {
|
|
const candlesClose = market.candles
|
|
?.map((candle) => candle?.close)
|
|
.filter((c: string | undefined): c is CandleClose => !isNil(c));
|
|
const candleLow = market.candles && calcCandleLow(market.candles);
|
|
const candleHigh = market.candles && calcCandleHigh(market.candles);
|
|
const handleKeyPress = (
|
|
event: React.KeyboardEvent<HTMLSpanElement>,
|
|
id: string
|
|
) => {
|
|
if (event.key === 'Enter' && onSelect) {
|
|
return onSelect(id);
|
|
}
|
|
};
|
|
const candleVolume = market.candles && calcCandleVolume(market.candles);
|
|
const selectMarketColumns: Column[] = [
|
|
{
|
|
kind: ColumnKind.Market,
|
|
value: (
|
|
<Link
|
|
to={`/markets/${market.id}`}
|
|
data-testid={`market-link-${market.id}`}
|
|
onKeyPress={(event) => handleKeyPress(event, market.id)}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
onSelect(market.id);
|
|
}}
|
|
>
|
|
<UILink>{market.tradableInstrument.instrument.code}</UILink>
|
|
</Link>
|
|
),
|
|
className: cellClassNames,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.ProductType,
|
|
value: market.tradableInstrument.instrument.product.__typename,
|
|
className: `py-2 first:text-left hidden sm:table-cell`,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.LastPrice,
|
|
value: market.data?.markPrice ? (
|
|
<PriceCell
|
|
value={Number(market.data.markPrice)}
|
|
valueFormatted={addDecimalsNormalizeNumber(
|
|
market.data.markPrice.toString(),
|
|
market.decimalPlaces,
|
|
2
|
|
)}
|
|
/>
|
|
) : (
|
|
'-'
|
|
),
|
|
className: cellClassNames,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.Change24,
|
|
value: candlesClose && (
|
|
<PriceCellChange
|
|
candles={candlesClose}
|
|
decimalPlaces={market.decimalPlaces}
|
|
/>
|
|
),
|
|
className: cellClassNames,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.Sparkline,
|
|
value: candlesClose && (
|
|
<Sparkline
|
|
width={100}
|
|
height={20}
|
|
muted={false}
|
|
data={candlesClose.map((c: string) => Number(c))}
|
|
/>
|
|
),
|
|
className: `${cellClassNames} hidden lg:table-cell`,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.Asset,
|
|
value: (
|
|
<button
|
|
data-dialog-trigger
|
|
className="inline underline"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (!onCellClick) return;
|
|
onCellClick(
|
|
e,
|
|
ColumnKind.Asset,
|
|
market.tradableInstrument.instrument.product.settlementAsset
|
|
.symbol
|
|
);
|
|
}}
|
|
>
|
|
{market.tradableInstrument.instrument.product.settlementAsset.symbol}
|
|
</button>
|
|
),
|
|
className: `${cellClassNames} hidden sm:table-cell`,
|
|
onlyOnDetailed: false,
|
|
},
|
|
{
|
|
kind: ColumnKind.High24,
|
|
value: candleHigh ? (
|
|
<PriceCell
|
|
value={Number(candleHigh)}
|
|
valueFormatted={addDecimalsNormalizeNumber(
|
|
candleHigh.toString(),
|
|
market.decimalPlaces,
|
|
2
|
|
)}
|
|
/>
|
|
) : (
|
|
'-'
|
|
),
|
|
className: `${cellClassNames} hidden xl:table-cell font-mono`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
{
|
|
kind: ColumnKind.Low24,
|
|
value: candleLow ? (
|
|
<PriceCell
|
|
value={Number(candleLow)}
|
|
valueFormatted={addDecimalsNormalizeNumber(
|
|
candleLow.toString(),
|
|
market.decimalPlaces,
|
|
2
|
|
)}
|
|
/>
|
|
) : (
|
|
'-'
|
|
),
|
|
className: `${cellClassNames} hidden xl:table-cell font-mono`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
{
|
|
kind: ColumnKind.Volume,
|
|
value: candleVolume
|
|
? addDecimalsNormalizeNumber(
|
|
candleVolume.toString(),
|
|
market.positionDecimalPlaces,
|
|
2
|
|
)
|
|
: '-',
|
|
className: `${cellClassNames} hidden lg:table-cell font-mono`,
|
|
onlyOnDetailed: true,
|
|
dataTestId: 'market-volume',
|
|
},
|
|
{
|
|
kind: ColumnKind.TradingMode,
|
|
value: <TradingMode market={market} />,
|
|
className: `${cellClassNames} hidden lg:table-cell`,
|
|
onlyOnDetailed: true,
|
|
dataTestId: 'trading-mode-col',
|
|
},
|
|
{
|
|
kind: ColumnKind.Fee,
|
|
value: <FeesCell feeFactors={market.fees.factors} />,
|
|
className: `${cellClassNames} hidden xl:table-cell font-mono`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
{
|
|
kind: ColumnKind.Position,
|
|
value: (
|
|
<p className={signedNumberCssClass(openVolume || '')}>{openVolume}</p>
|
|
),
|
|
className: `${cellClassNames} hidden xxl:table-cell font-mono`,
|
|
onlyOnDetailed: true,
|
|
},
|
|
];
|
|
return selectMarketColumns;
|
|
};
|