Feature/356 view a list of markets page and show additional useful information (#419)
* Initial commit after nx create * Add .env files * Add working commit for poc * Make deal-ticket-manager.tsx accept children as props and export more components to be consumed by external apps * Add stepper component to simple trading app * Add basic prototype for simple trading app with stepper component * simple market app - simple market list - initial commit * simple market app - simple market list - add some new changes * Resolve conflicts after rebase Initial commit after nx create * Add stepper component to simple trading app * simple market app - simple market list - remove wrongly added file after rebase * simple market app - simple market list - proposals of layout frame and percent change calculation * simple market app - simple market list - proposals of working solution * feat: [simple-app] - simple market list - clean up gQL queries * feat: [simple-app] - simple market list - indicate no auctionEnd * feat: [simple-app] - simple market list - a bunch of changes after review feedback * feat: [simple-app] - simple market list - get expire date from instrument tag * feat: [simple-app] - simple market list - a bunch of small improvements Co-authored-by: Elmar Gasimov <elmar@vegaprotocol.io> Co-authored-by: maciek <maciek@vegaprotocol.io>
This commit is contained in:
parent
a352702bc5
commit
ef18ac8483
@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"ignorePatterns": ["!**/*", "__generated__"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
|
@ -9,12 +9,14 @@ import {
|
||||
VegaManageDialog,
|
||||
VegaWalletProvider,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { DealTicketContainer } from './components/deal-ticket';
|
||||
// import { DealTicketContainer } from './components/deal-ticket';
|
||||
import { VegaWalletConnectButton } from './components/vega-wallet-connect-button';
|
||||
import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Connectors } from './lib/vega-connectors';
|
||||
import '../styles.scss';
|
||||
import { AppLoader } from './components/app-loader';
|
||||
import SimpleMarketList from './components/simple-market-list';
|
||||
|
||||
function App() {
|
||||
const [theme, toggleTheme] = useThemeSwitcher();
|
||||
@ -30,8 +32,8 @@ function App() {
|
||||
<ApolloProvider client={client}>
|
||||
<VegaWalletProvider>
|
||||
<AppLoader>
|
||||
<div className="h-full dark:bg-black dark:text-white-60 bg-white text-black-60 grid grid-rows-[min-content,1fr]">
|
||||
<div className="flex items-stretch border-b-[7px] border-vega-yellow">
|
||||
<div className="h-full max-h-full dark:bg-black dark:text-white-60 bg-white text-black-60 grid md:grid-rows-[min-content_1fr_min-content] lg:grid-cols-[375px_1fr] md:grid-cols-[200px_1fr] sm:grid-rows-[min-content_min-content_1fr_min-content]">
|
||||
<div className="flex items-stretch border-b-[7px] border-vega-yellow md:col-span-3">
|
||||
<div className="flex items-center gap-4 ml-auto mr-8">
|
||||
<VegaWalletConnectButton
|
||||
setConnectDialog={(open) =>
|
||||
@ -44,15 +46,25 @@ function App() {
|
||||
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
|
||||
</div>
|
||||
</div>
|
||||
<main>
|
||||
<div className="md:w-4/5 lg:w-3/5 xl:w-1/3 mx-auto">
|
||||
<DealTicketContainer
|
||||
|
||||
<aside className="md:col-start-1 md:col-end-1 md:row-start-2 md:row-end-2">
|
||||
<ul>
|
||||
<li>{t('Markets')}</li>
|
||||
<li>{t('Trade')}</li>
|
||||
<li>{t('Liquid')}</li>
|
||||
<li>{t('Markets')}</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<div className="md:col-start-2 md:col-end-2 md:row-start-2 md:row-end-2 overflow-auto">
|
||||
<SimpleMarketList />
|
||||
{/*<DealTicketContainer
|
||||
marketId={
|
||||
'0e4c4e0ce6626ea5c6bf5b5b510afadb3c91627aa9ff61e4c7e37ef8394f2c6f'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
/>*/}
|
||||
</div>
|
||||
|
||||
<footer className="md:col-span-3">®</footer>
|
||||
<VegaConnectDialog
|
||||
connectors={Connectors}
|
||||
dialogOpen={vegaWallet.connect}
|
||||
|
@ -0,0 +1,30 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { MarketState } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: SimpleMarketDataFields
|
||||
// ====================================================
|
||||
|
||||
export interface SimpleMarketDataFields_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
state: MarketState;
|
||||
}
|
||||
|
||||
export interface SimpleMarketDataFields {
|
||||
__typename: "MarketData";
|
||||
/**
|
||||
* market id of the associated mark price
|
||||
*/
|
||||
market: SimpleMarketDataFields_market;
|
||||
}
|
37
apps/simple-trading-app/src/app/components/simple-market-list/__generated__/SimpleMarketDataSub.ts
generated
Normal file
37
apps/simple-trading-app/src/app/components/simple-market-list/__generated__/SimpleMarketDataSub.ts
generated
Normal file
@ -0,0 +1,37 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { MarketState } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL subscription operation: SimpleMarketDataSub
|
||||
// ====================================================
|
||||
|
||||
export interface SimpleMarketDataSub_marketData_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
state: MarketState;
|
||||
}
|
||||
|
||||
export interface SimpleMarketDataSub_marketData {
|
||||
__typename: "MarketData";
|
||||
/**
|
||||
* market id of the associated mark price
|
||||
*/
|
||||
market: SimpleMarketDataSub_marketData_market;
|
||||
}
|
||||
|
||||
export interface SimpleMarketDataSub {
|
||||
/**
|
||||
* Subscribe to the mark price changes
|
||||
*/
|
||||
marketData: SimpleMarketDataSub_marketData;
|
||||
}
|
122
apps/simple-trading-app/src/app/components/simple-market-list/__generated__/SimpleMarkets.ts
generated
Normal file
122
apps/simple-trading-app/src/app/components/simple-market-list/__generated__/SimpleMarkets.ts
generated
Normal file
@ -0,0 +1,122 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { Interval, MarketState } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: SimpleMarkets
|
||||
// ====================================================
|
||||
|
||||
export interface SimpleMarkets_markets_data_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
state: MarketState;
|
||||
}
|
||||
|
||||
export interface SimpleMarkets_markets_data {
|
||||
__typename: "MarketData";
|
||||
/**
|
||||
* market id of the associated mark price
|
||||
*/
|
||||
market: SimpleMarkets_markets_data_market;
|
||||
}
|
||||
|
||||
export interface SimpleMarkets_markets_tradableInstrument_instrument_metadata {
|
||||
__typename: "InstrumentMetadata";
|
||||
/**
|
||||
* An arbitrary list of tags to associated to associate to the Instrument (string list)
|
||||
*/
|
||||
tags: string[] | null;
|
||||
}
|
||||
|
||||
export interface SimpleMarkets_markets_tradableInstrument_instrument_product_settlementAsset {
|
||||
__typename: "Asset";
|
||||
/**
|
||||
* The symbol of the asset (e.g: GBP)
|
||||
*/
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
export interface SimpleMarkets_markets_tradableInstrument_instrument_product {
|
||||
__typename: "Future";
|
||||
/**
|
||||
* The name of the asset (string)
|
||||
*/
|
||||
settlementAsset: SimpleMarkets_markets_tradableInstrument_instrument_product_settlementAsset;
|
||||
}
|
||||
|
||||
export interface SimpleMarkets_markets_tradableInstrument_instrument {
|
||||
__typename: "Instrument";
|
||||
/**
|
||||
* Metadata for this instrument
|
||||
*/
|
||||
metadata: SimpleMarkets_markets_tradableInstrument_instrument_metadata;
|
||||
/**
|
||||
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
|
||||
*/
|
||||
product: SimpleMarkets_markets_tradableInstrument_instrument_product;
|
||||
}
|
||||
|
||||
export interface SimpleMarkets_markets_tradableInstrument {
|
||||
__typename: "TradableInstrument";
|
||||
/**
|
||||
* An instance of or reference to a fully specified instrument.
|
||||
*/
|
||||
instrument: SimpleMarkets_markets_tradableInstrument_instrument;
|
||||
}
|
||||
|
||||
export interface SimpleMarkets_markets_candles {
|
||||
__typename: "Candle";
|
||||
/**
|
||||
* Open price (uint64)
|
||||
*/
|
||||
open: string;
|
||||
/**
|
||||
* Close price (uint64)
|
||||
*/
|
||||
close: string;
|
||||
}
|
||||
|
||||
export interface SimpleMarkets_markets {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Market full name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* marketData for the given market
|
||||
*/
|
||||
data: SimpleMarkets_markets_data | null;
|
||||
/**
|
||||
* An instance of or reference to a tradable instrument.
|
||||
*/
|
||||
tradableInstrument: SimpleMarkets_markets_tradableInstrument;
|
||||
/**
|
||||
* Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by params
|
||||
*/
|
||||
candles: (SimpleMarkets_markets_candles | null)[] | null;
|
||||
}
|
||||
|
||||
export interface SimpleMarkets {
|
||||
/**
|
||||
* One or more instruments that are trading on the VEGA network
|
||||
*/
|
||||
markets: SimpleMarkets_markets[] | null;
|
||||
}
|
||||
|
||||
export interface SimpleMarketsVariables {
|
||||
CandleInterval: Interval;
|
||||
CandleSince: string;
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { TailwindIntents } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
|
||||
export const MARKET_STATUS: Record<MarketState | '', TailwindIntents> = {
|
||||
[MarketState.Active]: TailwindIntents.Success,
|
||||
[MarketState.Cancelled]: TailwindIntents.Highlight,
|
||||
[MarketState.Closed]: TailwindIntents.Help,
|
||||
[MarketState.Pending]: TailwindIntents.Warning,
|
||||
[MarketState.Proposed]: TailwindIntents.Prompt,
|
||||
[MarketState.Rejected]: TailwindIntents.Danger,
|
||||
[MarketState.Settled]: TailwindIntents.Highlight,
|
||||
[MarketState.Suspended]: TailwindIntents.Warning,
|
||||
[MarketState.TradingTerminated]: TailwindIntents.Danger,
|
||||
'': TailwindIntents.Highlight,
|
||||
};
|
@ -0,0 +1,84 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { makeDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
SimpleMarkets,
|
||||
SimpleMarkets_markets,
|
||||
} from './__generated__/SimpleMarkets';
|
||||
import type {
|
||||
SimpleMarketDataSub,
|
||||
SimpleMarketDataSub_marketData,
|
||||
} from './__generated__/SimpleMarketDataSub';
|
||||
|
||||
const MARKET_DATA_FRAGMENT = gql`
|
||||
fragment SimpleMarketDataFields on MarketData {
|
||||
market {
|
||||
id
|
||||
state
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const MARKETS_QUERY = gql`
|
||||
${MARKET_DATA_FRAGMENT}
|
||||
query SimpleMarkets($CandleInterval: Interval!, $CandleSince: String!) {
|
||||
markets {
|
||||
id
|
||||
name
|
||||
data {
|
||||
...SimpleMarketDataFields
|
||||
}
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
metadata {
|
||||
tags
|
||||
}
|
||||
product {
|
||||
... on Future {
|
||||
settlementAsset {
|
||||
symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
candles(interval: $CandleInterval, since: $CandleSince) {
|
||||
open
|
||||
close
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const MARKET_DATA_SUB = gql`
|
||||
${MARKET_DATA_FRAGMENT}
|
||||
subscription SimpleMarketDataSub {
|
||||
marketData {
|
||||
...SimpleMarketDataFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const update = (
|
||||
draft: SimpleMarkets_markets[],
|
||||
delta: SimpleMarketDataSub_marketData
|
||||
) => {
|
||||
const index = draft.findIndex((m) => m.id === delta.market.id);
|
||||
if (index !== -1) {
|
||||
draft[index].data = delta;
|
||||
}
|
||||
// @TODO - else push new market to draft
|
||||
};
|
||||
|
||||
const getData = (responseData: SimpleMarkets) => responseData.markets;
|
||||
const getDelta = (
|
||||
subscriptionData: SimpleMarketDataSub
|
||||
): SimpleMarketDataSub_marketData => subscriptionData.marketData;
|
||||
|
||||
export const dataProvider = makeDataProvider<
|
||||
SimpleMarkets,
|
||||
SimpleMarkets_markets[],
|
||||
SimpleMarketDataSub,
|
||||
SimpleMarketDataSub_marketData
|
||||
>(MARKETS_QUERY, MARKET_DATA_SUB, update, getData, getDelta);
|
||||
|
||||
export default dataProvider;
|
@ -0,0 +1 @@
|
||||
export { default } from './simple-market-list';
|
@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import SimpleMarketExpires from './simple-market-expires';
|
||||
|
||||
describe('SimpleMarketExpires', () => {
|
||||
describe('should properly parse different tags', () => {
|
||||
it('settlement:date', () => {
|
||||
const tags = [
|
||||
'foo:buzz',
|
||||
'test:20220625T1200',
|
||||
'settlement',
|
||||
'settlement:notadate',
|
||||
'settlement:20220525T1200',
|
||||
];
|
||||
render(<SimpleMarketExpires tags={tags} />);
|
||||
expect(screen.getByText('expires 25 May 2022 12:00')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('settlement-date:date', () => {
|
||||
const tags = [
|
||||
'settlement',
|
||||
'settlement:20220525T1200',
|
||||
'settlement-date:2022-04-25T1200',
|
||||
];
|
||||
render(<SimpleMarketExpires tags={tags} />);
|
||||
expect(
|
||||
screen.getByText('expires 25 April 2022 12:00')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('last one proper tag should matter', () => {
|
||||
const tags = [
|
||||
'settlement',
|
||||
'settlement-date:20220525T1200',
|
||||
'settlement-expiry-date:2022-03-25T12:00:00',
|
||||
];
|
||||
render(<SimpleMarketExpires tags={tags} />);
|
||||
expect(
|
||||
screen.getByText('expires 25 March 2022 12:00')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('when no proper tag nor date should be null', () => {
|
||||
const tags = [
|
||||
'settlement',
|
||||
'settlemenz:20220525T1200',
|
||||
'settlemenx-date:20220425T1200',
|
||||
];
|
||||
const { container } = render(<SimpleMarketExpires tags={tags} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { format, isValid, parseISO } from 'date-fns';
|
||||
import { DATE_FORMAT } from '../../constants';
|
||||
|
||||
const SimpleMarketExpires = ({
|
||||
tags,
|
||||
}: {
|
||||
tags: ReadonlyArray<string> | null;
|
||||
}) => {
|
||||
if (tags) {
|
||||
const dateFound = tags.reduce<Date | null>((agg, tag) => {
|
||||
const parsed = parseISO(
|
||||
(tag.match(/^settlement.*:/) &&
|
||||
tag
|
||||
.split(':')
|
||||
.filter((item, i) => i)
|
||||
.join(':')) as string
|
||||
);
|
||||
if (isValid(parsed)) {
|
||||
agg = parsed;
|
||||
}
|
||||
return agg;
|
||||
}, null);
|
||||
return dateFound ? (
|
||||
<div className="py-2">{`${t('expires')} ${format(
|
||||
dateFound as Date,
|
||||
DATE_FORMAT
|
||||
)}`}</div>
|
||||
) : null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default SimpleMarketExpires;
|
@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import SimpleMarketList from './simple-market-list';
|
||||
import type { SimpleMarkets_markets } from './__generated__/SimpleMarkets';
|
||||
|
||||
jest.mock('./data-provider', () => jest.fn());
|
||||
|
||||
jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
useDataProvider: jest.fn(),
|
||||
t: (txt: string) => txt,
|
||||
}));
|
||||
|
||||
describe('SimpleMarketList', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should be properly renderer as empty', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(useDataProvider as unknown as jest.SpyInstance<any>).mockImplementation(
|
||||
() => ({ data: [], error: false, loading: false })
|
||||
);
|
||||
render(<SimpleMarketList />);
|
||||
expect(screen.getByText('No data to display')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should be properly rendered with some data', () => {
|
||||
const data = [
|
||||
{
|
||||
id: '1',
|
||||
data: {
|
||||
market: {
|
||||
state: MarketState.Active,
|
||||
},
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
settlementAsset: {
|
||||
symbol: 'tUSD',
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: {
|
||||
market: {
|
||||
state: MarketState.Proposed,
|
||||
},
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
settlementAsset: {
|
||||
symbol: 'ETH',
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
] as unknown as SimpleMarkets_markets[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(useDataProvider as unknown as jest.SpyInstance<any>).mockImplementation(
|
||||
() => ({ data, error: false, loading: false })
|
||||
);
|
||||
render(<SimpleMarketList />);
|
||||
expect(screen.getByRole('list')).toBeInTheDocument();
|
||||
expect(screen.getAllByRole('listitem')).toHaveLength(2);
|
||||
});
|
||||
});
|
@ -0,0 +1,78 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { AsyncRenderer, Lozenge, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import SimpleMarketPercentChange from './simple-market-percent-change';
|
||||
import SimpleMarketExpires from './simple-market-expires';
|
||||
import DataProvider from './data-provider';
|
||||
import { MARKET_STATUS } from './constants';
|
||||
|
||||
const SimpleMarketList = () => {
|
||||
const variables = useMemo(
|
||||
() => ({
|
||||
CandleInterval: 'I1H',
|
||||
CandleSince: new Date(Date.now() - 24 * 60 * 60 * 1000).toJSON(),
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const { data, error, loading } = useDataProvider(
|
||||
DataProvider,
|
||||
undefined, // @TODO - if we need a live update in the future
|
||||
variables
|
||||
);
|
||||
const onClick = useCallback((marketId) => {
|
||||
// @TODO - let's try to have navigation first
|
||||
console.log('trigger market', marketId);
|
||||
}, []);
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
{data && data.length > 0 ? (
|
||||
<ul className="list-none relative pt-8 pb-8">
|
||||
{data?.map((market) => (
|
||||
<li
|
||||
className="w-full relative flex justify-start items-center no-underline box-border text-left pt-8 pb-8 pl-16 pr-16 mb-10"
|
||||
key={market.id}
|
||||
>
|
||||
<div className="w-full grid sm:grid-cols-2">
|
||||
<div className="w-full grid sm:auto-rows-auto">
|
||||
<div className="font-extrabold py-2">{market.name}</div>
|
||||
<SimpleMarketExpires
|
||||
tags={market.tradableInstrument.instrument.metadata.tags}
|
||||
/>
|
||||
<div className="py-2">{`${t('settled in')} ${
|
||||
market.tradableInstrument.instrument.product.settlementAsset
|
||||
.symbol
|
||||
}`}</div>
|
||||
</div>
|
||||
<div className="w-full grid sm:grid-rows-2">
|
||||
<div>
|
||||
<SimpleMarketPercentChange candles={market.candles} />
|
||||
</div>
|
||||
<div>
|
||||
<Lozenge
|
||||
variant={MARKET_STATUS[market.data?.market.state || '']}
|
||||
>
|
||||
{market.data?.market.state}
|
||||
</Lozenge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute right-16 top-1/2 -translate-y-1/2">
|
||||
<Button
|
||||
onClick={() => onClick(market.id)}
|
||||
variant="inline"
|
||||
prependIconName="chevron-right"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<Splash>{t('No data to display')}</Splash>
|
||||
)}
|
||||
</AsyncRenderer>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimpleMarketList;
|
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { theme } from '@vegaprotocol/tailwindcss-config';
|
||||
import SimpleMarketPercentChange from './simple-market-percent-change';
|
||||
import type { SimpleMarkets_markets_candles } from './__generated__/SimpleMarkets';
|
||||
|
||||
describe('SimpleMarketPercentChange should parse proper change', () => {
|
||||
let candles: (SimpleMarkets_markets_candles | null)[] | null;
|
||||
it('empty array', () => {
|
||||
candles = [];
|
||||
render(<SimpleMarketPercentChange candles={candles} />);
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
it('null', () => {
|
||||
candles = null;
|
||||
render(<SimpleMarketPercentChange candles={candles} />);
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
it('an appreciated one', () => {
|
||||
candles = [
|
||||
{ open: '50' } as SimpleMarkets_markets_candles,
|
||||
{ close: '100' } as SimpleMarkets_markets_candles,
|
||||
null,
|
||||
];
|
||||
render(<SimpleMarketPercentChange candles={candles} />);
|
||||
expect(screen.getByText('100.000%')).toBeInTheDocument();
|
||||
expect(screen.getByText('100.000%')).toHaveStyle(
|
||||
`color: ${theme.colors.vega.green}`
|
||||
);
|
||||
});
|
||||
it('a depreciated one', () => {
|
||||
candles = [
|
||||
{ open: '100' } as SimpleMarkets_markets_candles,
|
||||
{ close: '50' } as SimpleMarkets_markets_candles,
|
||||
null,
|
||||
];
|
||||
render(<SimpleMarketPercentChange candles={candles} />);
|
||||
expect(screen.getByText('-50.000%')).toBeInTheDocument();
|
||||
expect(screen.getByText('-50.000%')).toHaveStyle(
|
||||
`color: ${theme.colors.vega.pink}`
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { theme } from '@vegaprotocol/tailwindcss-config';
|
||||
import type { SimpleMarkets_markets_candles } from './__generated__/SimpleMarkets';
|
||||
|
||||
interface Props {
|
||||
candles: (SimpleMarkets_markets_candles | null)[] | null;
|
||||
}
|
||||
|
||||
const getChange = (
|
||||
candles: (SimpleMarkets_markets_candles | null)[] | null
|
||||
) => {
|
||||
if (candles) {
|
||||
const first = parseInt(candles.find((item) => item?.open)?.open || '-1');
|
||||
const last = candles.reduceRight((aggr, item) => {
|
||||
if (aggr === -1 && item?.close) {
|
||||
aggr = parseInt(item.close);
|
||||
}
|
||||
return aggr;
|
||||
}, -1);
|
||||
if (first !== -1 && last !== -1) {
|
||||
return Number(((last - first) / first) * 100).toFixed(3) + '%';
|
||||
}
|
||||
}
|
||||
return ' - ';
|
||||
};
|
||||
|
||||
const getColor = (change: number | string) => {
|
||||
if (parseFloat(change as string) > 0) {
|
||||
return theme.colors.vega.green;
|
||||
}
|
||||
if (parseFloat(change as string) < 0) {
|
||||
return theme.colors.vega.pink;
|
||||
}
|
||||
return theme.colors.intent.highlight;
|
||||
};
|
||||
|
||||
const SimpleMarketPercentChange = ({ candles }: Props) => {
|
||||
const change = getChange(candles);
|
||||
const color = getColor(change);
|
||||
return <p style={{ color }}>{change}</p>;
|
||||
};
|
||||
|
||||
export default SimpleMarketPercentChange;
|
1
apps/simple-trading-app/src/app/constants/index.ts
Normal file
1
apps/simple-trading-app/src/app/constants/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export const DATE_FORMAT = 'dd MMMM yyyy HH:mm';
|
@ -9,6 +9,6 @@
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="root" class="h-full max-h-full"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,7 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"],
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"],
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
|
Loading…
Reference in New Issue
Block a user