feat: no markets (#2097)
* feat: no markets * feat: no markets * feat: no markets * feat: no markets * feat: no markets
This commit is contained in:
parent
98a3d2267b
commit
8e5012891c
@ -1,7 +1,53 @@
|
|||||||
import { aliasQuery } from '@vegaprotocol/cypress';
|
import { aliasQuery } from '@vegaprotocol/cypress';
|
||||||
|
import type { ProposalListFieldsFragment } from '@vegaprotocol/governance';
|
||||||
|
import { Schema } from '@vegaprotocol/types';
|
||||||
|
|
||||||
const selectMarketOverlay = 'select-market-list';
|
const selectMarketOverlay = 'select-market-list';
|
||||||
|
|
||||||
|
const generateProposal = (code: string): ProposalListFieldsFragment => ({
|
||||||
|
__typename: 'Proposal',
|
||||||
|
reference: '',
|
||||||
|
state: Schema.ProposalState.STATE_OPEN,
|
||||||
|
datetime: '',
|
||||||
|
votes: {
|
||||||
|
__typename: undefined,
|
||||||
|
yes: {
|
||||||
|
__typename: undefined,
|
||||||
|
totalTokens: '',
|
||||||
|
totalNumber: '',
|
||||||
|
totalWeight: '',
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
__typename: undefined,
|
||||||
|
totalTokens: '',
|
||||||
|
totalNumber: '',
|
||||||
|
totalWeight: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
terms: {
|
||||||
|
__typename: 'ProposalTerms',
|
||||||
|
closingDatetime: '',
|
||||||
|
enactmentDatetime: undefined,
|
||||||
|
change: {
|
||||||
|
__typename: 'NewMarket',
|
||||||
|
instrument: {
|
||||||
|
__typename: 'InstrumentConfiguration',
|
||||||
|
code: code,
|
||||||
|
name: code,
|
||||||
|
futureProduct: {
|
||||||
|
__typename: 'FutureProduct',
|
||||||
|
settlementAsset: {
|
||||||
|
__typename: 'Asset',
|
||||||
|
id: 'A',
|
||||||
|
name: 'A',
|
||||||
|
symbol: 'A',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
describe('home', { tags: '@regression' }, () => {
|
describe('home', { tags: '@regression' }, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.mockTradingPage();
|
cy.mockTradingPage();
|
||||||
@ -51,9 +97,8 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('no default found', () => {
|
describe('no markets found', () => {
|
||||||
it('redirects to a the market list page if no sensible default is found', () => {
|
it('redirects to a the empty market page and displays welcome notice', () => {
|
||||||
// Mock markets query that is triggered by home page to find default market
|
|
||||||
cy.mockGQL((req) => {
|
cy.mockGQL((req) => {
|
||||||
const data = {
|
const data = {
|
||||||
marketsConnection: {
|
marketsConnection: {
|
||||||
@ -61,13 +106,30 @@ describe('home', { tags: '@regression' }, () => {
|
|||||||
edges: [],
|
edges: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const proposalA: ProposalListFieldsFragment =
|
||||||
|
generateProposal('AAAZZZ');
|
||||||
|
|
||||||
aliasQuery(req, 'Markets', data);
|
aliasQuery(req, 'Markets', data);
|
||||||
aliasQuery(req, 'MarketsData', data);
|
aliasQuery(req, 'MarketsData', data);
|
||||||
|
aliasQuery(req, 'ProposalsList', {
|
||||||
|
proposalsConnection: {
|
||||||
|
__typename: 'ProposalsConnection',
|
||||||
|
edges: [{ __typename: 'ProposalEdge', node: proposalA }],
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
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/empty`);
|
||||||
|
cy.getByTestId('welcome-notice-title').should(
|
||||||
|
'contain.text',
|
||||||
|
'Welcome to Console'
|
||||||
|
);
|
||||||
|
cy.getByTestId('welcome-notice-proposed-markets').should(
|
||||||
|
'contain.text',
|
||||||
|
'AAAZZZ'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
useDataProvider,
|
useDataProvider,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { EMPTY_MARKET_ID } from '../../components/constants';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useGlobalStore, usePageTitleStore } from '../../stores';
|
import { useGlobalStore, usePageTitleStore } from '../../stores';
|
||||||
@ -27,9 +28,8 @@ export const Home = () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
update({ landingDialog: true });
|
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
update({ landingDialog: data.length > 0 });
|
||||||
const marketId = data[0]?.id;
|
const marketId = data[0]?.id;
|
||||||
const marketName = data[0]?.tradableInstrument.instrument.name;
|
const marketName = data[0]?.tradableInstrument.instrument.name;
|
||||||
const marketPrice = data[0]?.data?.markPrice
|
const marketPrice = data[0]?.data?.markPrice
|
||||||
@ -46,10 +46,8 @@ export const Home = () => {
|
|||||||
if (pageTitle !== newPageTitle) {
|
if (pageTitle !== newPageTitle) {
|
||||||
updateTitle(newPageTitle);
|
updateTitle(newPageTitle);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
// Fallback to the markets list page
|
navigate(`/markets/${EMPTY_MARKET_ID}`);
|
||||||
else {
|
|
||||||
navigate('/markets');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [data, navigate, riskNoticeDialog, update, pageTitle, updateTitle]);
|
}, [data, navigate, riskNoticeDialog, update, pageTitle, updateTitle]);
|
||||||
|
@ -14,11 +14,14 @@ import type {
|
|||||||
Candle,
|
Candle,
|
||||||
MarketDataUpdateFieldsFragment,
|
MarketDataUpdateFieldsFragment,
|
||||||
} from '@vegaprotocol/market-list';
|
} from '@vegaprotocol/market-list';
|
||||||
|
|
||||||
import { marketProvider, marketDataProvider } from '@vegaprotocol/market-list';
|
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';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import { EMPTY_MARKET_ID } from '../../components/constants';
|
||||||
|
import { useWelcomeNoticeDialog } from '../../components/welcome-notice';
|
||||||
|
|
||||||
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
|
const calculatePrice = (markPrice?: string, decimalPlaces?: number) => {
|
||||||
return markPrice && decimalPlaces
|
return markPrice && decimalPlaces
|
||||||
@ -40,7 +43,9 @@ export const Market = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const marketId = params.marketId;
|
const isEmpty = params.marketId === EMPTY_MARKET_ID;
|
||||||
|
const marketId = isEmpty ? undefined : params.marketId;
|
||||||
|
|
||||||
const { w } = useWindowSize();
|
const { w } = useWindowSize();
|
||||||
const { landingDialog, riskNoticeDialog, update } = useGlobalStore(
|
const { landingDialog, riskNoticeDialog, update } = useGlobalStore(
|
||||||
(store) => ({
|
(store) => ({
|
||||||
@ -108,17 +113,16 @@ export const Market = ({
|
|||||||
skip: !marketId || !data,
|
skip: !marketId || !data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useWelcomeNoticeDialog();
|
||||||
|
|
||||||
const tradeView = useMemo(() => {
|
const tradeView = useMemo(() => {
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (w > 960) {
|
if (w > 960) {
|
||||||
return <TradeGrid market={data} onSelect={onSelect} />;
|
return <TradeGrid market={data} onSelect={onSelect} />;
|
||||||
}
|
}
|
||||||
return <TradePanels market={data} onSelect={onSelect} />;
|
return <TradePanels market={data} onSelect={onSelect} />;
|
||||||
}, [w, data, onSelect]);
|
}, [w, data, onSelect]);
|
||||||
|
|
||||||
if (!marketId) {
|
if (!marketId && !isEmpty) {
|
||||||
return (
|
return (
|
||||||
<Splash>
|
<Splash>
|
||||||
<p>{t('Not found')}</p>
|
<p>{t('Not found')}</p>
|
||||||
@ -131,8 +135,9 @@ export const Market = ({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
data={data || undefined}
|
data={data || undefined}
|
||||||
|
noDataCondition={(data) => false}
|
||||||
render={(data) => {
|
render={(data) => {
|
||||||
if (!data) {
|
if (!data && !isEmpty) {
|
||||||
return <Splash>{t('Market not found')}</Splash>;
|
return <Splash>{t('Market not found')}</Splash>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -9,7 +9,7 @@ import { LayoutPriority } from 'allotment';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import { memo, useState } from 'react';
|
import { memo, useState } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode, ComponentProps } from 'react';
|
||||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||||
import {
|
import {
|
||||||
@ -19,6 +19,7 @@ import {
|
|||||||
ResizableGridPanel,
|
ResizableGridPanel,
|
||||||
ButtonLink,
|
ButtonLink,
|
||||||
Link,
|
Link,
|
||||||
|
Splash,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||||
@ -36,13 +37,32 @@ import { MarketMarkPrice } from '../../components/market-mark-price';
|
|||||||
import { MarketTradingModeComponent } from '../../components/market-trading-mode';
|
import { MarketTradingModeComponent } from '../../components/market-trading-mode';
|
||||||
import { Last24hVolume } from '../../components/last-24h-volume';
|
import { Last24hVolume } from '../../components/last-24h-volume';
|
||||||
|
|
||||||
|
const NO_MARKET = t('No market');
|
||||||
|
|
||||||
|
type MarketDependantView =
|
||||||
|
| typeof CandlesChartContainer
|
||||||
|
| typeof DepthChartContainer
|
||||||
|
| typeof DealTicketContainer
|
||||||
|
| typeof MarketInfoContainer
|
||||||
|
| typeof OrderbookContainer
|
||||||
|
| typeof TradesContainer;
|
||||||
|
|
||||||
|
type MarketDependantViewProps = ComponentProps<MarketDependantView>;
|
||||||
|
|
||||||
|
const requiresMarket = (View: MarketDependantView) => {
|
||||||
|
const WrappedComponent = (props: MarketDependantViewProps) =>
|
||||||
|
props.marketId ? <View {...props} /> : <Splash>{NO_MARKET}</Splash>;
|
||||||
|
WrappedComponent.displayName = `RequiresMarket(${View.name})`;
|
||||||
|
return WrappedComponent;
|
||||||
|
};
|
||||||
|
|
||||||
const TradingViews = {
|
const TradingViews = {
|
||||||
Candles: CandlesChartContainer,
|
Candles: requiresMarket(CandlesChartContainer),
|
||||||
Depth: DepthChartContainer,
|
Depth: requiresMarket(DepthChartContainer),
|
||||||
Ticket: DealTicketContainer,
|
Ticket: requiresMarket(DealTicketContainer),
|
||||||
Info: MarketInfoContainer,
|
Info: requiresMarket(MarketInfoContainer),
|
||||||
Orderbook: OrderbookContainer,
|
Orderbook: requiresMarket(OrderbookContainer),
|
||||||
Trades: TradesContainer,
|
Trades: requiresMarket(TradesContainer),
|
||||||
Positions: PositionsContainer,
|
Positions: PositionsContainer,
|
||||||
Orders: OrderListContainer,
|
Orders: OrderListContainer,
|
||||||
Collateral: AccountsContainer,
|
Collateral: AccountsContainer,
|
||||||
@ -52,11 +72,11 @@ const TradingViews = {
|
|||||||
type TradingView = keyof typeof TradingViews;
|
type TradingView = keyof typeof TradingViews;
|
||||||
|
|
||||||
type ExpiryLabelProps = {
|
type ExpiryLabelProps = {
|
||||||
market: SingleMarketFieldsFragment;
|
market: SingleMarketFieldsFragment | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
|
const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
|
||||||
const content = getExpiryDate(market);
|
const content = market ? getExpiryDate(market) : '-';
|
||||||
return <div data-testid="trading-expiry">{content}</div>;
|
return <div data-testid="trading-expiry">{content}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,7 +89,7 @@ const ExpiryTooltipContent = ({
|
|||||||
market,
|
market,
|
||||||
explorerUrl,
|
explorerUrl,
|
||||||
}: ExpiryTooltipContentProps) => {
|
}: ExpiryTooltipContentProps) => {
|
||||||
if (market.marketTimestamps.close === null) {
|
if (market?.marketTimestamps.close === null) {
|
||||||
const oracleId =
|
const oracleId =
|
||||||
market.tradableInstrument.instrument.product
|
market.tradableInstrument.instrument.product
|
||||||
.dataSourceSpecForTradingTermination?.id;
|
.dataSourceSpecForTradingTermination?.id;
|
||||||
@ -94,7 +114,7 @@ const ExpiryTooltipContent = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface TradeMarketHeaderProps {
|
interface TradeMarketHeaderProps {
|
||||||
market: SingleMarketFieldsFragment;
|
market: SingleMarketFieldsFragment | null;
|
||||||
onSelect: (marketId: string) => void;
|
onSelect: (marketId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +126,7 @@ export const TradeMarketHeader = ({
|
|||||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||||
|
|
||||||
const symbol =
|
const symbol =
|
||||||
market.tradableInstrument.instrument.product?.settlementAsset?.symbol;
|
market?.tradableInstrument.instrument.product?.settlementAsset?.symbol;
|
||||||
|
|
||||||
const onCellClick: OnCellClickHandler = (e, kind, value) => {
|
const onCellClick: OnCellClickHandler = (e, kind, value) => {
|
||||||
if (value && kind === ColumnKind.Asset) {
|
if (value && kind === ColumnKind.Asset) {
|
||||||
@ -118,7 +138,7 @@ export const TradeMarketHeader = ({
|
|||||||
<Header
|
<Header
|
||||||
title={
|
title={
|
||||||
<SelectMarketPopover
|
<SelectMarketPopover
|
||||||
marketName={market.tradableInstrument.instrument.name}
|
marketName={market?.tradableInstrument.instrument.name || NO_MARKET}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
onCellClick={onCellClick}
|
onCellClick={onCellClick}
|
||||||
/>
|
/>
|
||||||
@ -127,19 +147,21 @@ export const TradeMarketHeader = ({
|
|||||||
<HeaderStat
|
<HeaderStat
|
||||||
heading={t('Expiry')}
|
heading={t('Expiry')}
|
||||||
description={
|
description={
|
||||||
<ExpiryTooltipContent
|
market && (
|
||||||
market={market}
|
<ExpiryTooltipContent
|
||||||
explorerUrl={VEGA_EXPLORER_URL}
|
market={market}
|
||||||
/>
|
explorerUrl={VEGA_EXPLORER_URL}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
testId="market-expiry"
|
testId="market-expiry"
|
||||||
>
|
>
|
||||||
<ExpiryLabel market={market} />
|
<ExpiryLabel market={market} />
|
||||||
</HeaderStat>
|
</HeaderStat>
|
||||||
<MarketMarkPrice marketId={market.id} />
|
<MarketMarkPrice marketId={market?.id} />
|
||||||
<Last24hPriceChange marketId={market.id} />
|
<Last24hPriceChange marketId={market?.id} />
|
||||||
<Last24hVolume marketId={market.id} />
|
<Last24hVolume marketId={market?.id} />
|
||||||
<MarketTradingModeComponent marketId={market.id} onSelect={onSelect} />
|
<MarketTradingModeComponent marketId={market?.id} onSelect={onSelect} />
|
||||||
{symbol ? (
|
{symbol ? (
|
||||||
<HeaderStat
|
<HeaderStat
|
||||||
heading={t('Settlement asset')}
|
heading={t('Settlement asset')}
|
||||||
@ -161,7 +183,7 @@ export const TradeMarketHeader = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface TradeGridProps {
|
interface TradeGridProps {
|
||||||
market: SingleMarketFieldsFragment;
|
market: SingleMarketFieldsFragment | null;
|
||||||
onSelect: (marketId: string) => void;
|
onSelect: (marketId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +192,7 @@ const MainGrid = ({
|
|||||||
onSelect,
|
onSelect,
|
||||||
}: {
|
}: {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
onSelect: (marketId: string) => void;
|
onSelect?: (marketId: string) => void;
|
||||||
}) => (
|
}) => (
|
||||||
<ResizableGrid vertical>
|
<ResizableGrid vertical>
|
||||||
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
|
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
|
||||||
@ -205,7 +227,7 @@ const MainGrid = ({
|
|||||||
<TradingViews.Info
|
<TradingViews.Info
|
||||||
marketId={marketId}
|
marketId={marketId}
|
||||||
onSelect={(id: string) => {
|
onSelect={(id: string) => {
|
||||||
onSelect(id);
|
onSelect?.(id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
@ -260,7 +282,7 @@ export const TradeGrid = ({ market, onSelect }: TradeGridProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className="h-full grid grid-rows-[min-content_1fr]">
|
<div className="h-full grid grid-rows-[min-content_1fr]">
|
||||||
<TradeMarketHeader market={market} onSelect={onSelect} />
|
<TradeMarketHeader market={market} onSelect={onSelect} />
|
||||||
<MainGridWrapped marketId={market.id} onSelect={onSelect} />
|
<MainGridWrapped marketId={market?.id || ''} onSelect={onSelect} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -280,7 +302,7 @@ const TradeGridChild = ({ children }: TradeGridChildProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface TradePanelsProps {
|
interface TradePanelsProps {
|
||||||
market: SingleMarketFieldsFragment;
|
market: SingleMarketFieldsFragment | null;
|
||||||
onSelect: (marketId: string) => void;
|
onSelect: (marketId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +319,9 @@ export const TradePanels = ({ market, onSelect }: TradePanelsProps) => {
|
|||||||
throw new Error(`No component for view: ${view}`);
|
throw new Error(`No component for view: ${view}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Component marketId={market.id} onSelect={onSelect} />;
|
if (!market) return <Splash>{NO_MARKET}</Splash>;
|
||||||
|
|
||||||
|
return <Component marketId={market?.id} onSelect={onSelect} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -2,13 +2,15 @@ import { useCallback } from 'react';
|
|||||||
import { MarketsContainer } from '@vegaprotocol/market-list';
|
import { MarketsContainer } from '@vegaprotocol/market-list';
|
||||||
import { useGlobalStore } from '../../stores';
|
import { useGlobalStore } from '../../stores';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useWelcomeNoticeDialog } from '../../components/welcome-notice';
|
||||||
|
|
||||||
export const Markets = () => {
|
export const Markets = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { update } = useGlobalStore((store) => ({ update: store.update }));
|
const { update } = useGlobalStore((store) => ({ update: store.update }));
|
||||||
|
useWelcomeNoticeDialog();
|
||||||
const handleOnSelect = useCallback(
|
const handleOnSelect = useCallback(
|
||||||
(marketId: string) => {
|
(marketId: string) => {
|
||||||
update({ marketId });
|
update({ marketId, welcomeNoticeDialog: false });
|
||||||
navigate(`/markets/${marketId}`);
|
navigate(`/markets/${marketId}`);
|
||||||
},
|
},
|
||||||
[update, navigate]
|
[update, navigate]
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
export const DEBOUNCE_UPDATE_TIME = 500;
|
export const DEBOUNCE_UPDATE_TIME = 500;
|
||||||
|
export const EMPTY_MARKET_ID = 'empty';
|
||||||
|
|
||||||
|
// Token dApp
|
||||||
export const TOKEN_DEFAULT_DOMAIN = 'https://token.fairground.wtf';
|
export const TOKEN_DEFAULT_DOMAIN = 'https://token.fairground.wtf';
|
||||||
|
export const GOVERNANCE_LINK = '/governance';
|
||||||
export const NEW_PROPOSAL_LINK = '/governance/propose/new-market';
|
export const NEW_PROPOSAL_LINK = '/governance/propose/new-market';
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
import { HeaderStat } from '../header';
|
import { HeaderStat } from '../header';
|
||||||
import * as constants from '../constants';
|
import * as constants from '../constants';
|
||||||
|
|
||||||
export const Last24hPriceChange = ({ marketId }: { marketId: string }) => {
|
export const Last24hPriceChange = ({ marketId }: { marketId?: string }) => {
|
||||||
const [candlesClose, setCandlesClose] = useState<string[]>([]);
|
const [candlesClose, setCandlesClose] = useState<string[]>([]);
|
||||||
const yesterday = useYesterday();
|
const yesterday = useYesterday();
|
||||||
// Cache timestamp for yesterday to prevent full unmount of market page when
|
// Cache timestamp for yesterday to prevent full unmount of market page when
|
||||||
|
@ -20,7 +20,7 @@ import type {
|
|||||||
SingleMarketFieldsFragment,
|
SingleMarketFieldsFragment,
|
||||||
Candle,
|
Candle,
|
||||||
} from '@vegaprotocol/market-list';
|
} from '@vegaprotocol/market-list';
|
||||||
export const Last24hVolume = ({ marketId }: { marketId: string }) => {
|
export const Last24hVolume = ({ marketId }: { marketId?: string }) => {
|
||||||
const [candleVolume, setCandleVolume] = useState<string>();
|
const [candleVolume, setCandleVolume] = useState<string>();
|
||||||
const yesterday = useYesterday();
|
const yesterday = useYesterday();
|
||||||
// Cache timestamp for yesterday to prevent full unmount of market page when
|
// Cache timestamp for yesterday to prevent full unmount of market page when
|
||||||
|
@ -14,7 +14,7 @@ import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
|
|||||||
import { HeaderStat } from '../header';
|
import { HeaderStat } from '../header';
|
||||||
import * as constants from '../constants';
|
import * as constants from '../constants';
|
||||||
|
|
||||||
export const MarketMarkPrice = ({ marketId }: { marketId: string }) => {
|
export const MarketMarkPrice = ({ marketId }: { marketId?: string }) => {
|
||||||
const [marketPrice, setMarketPrice] = useState<string | null>(null);
|
const [marketPrice, setMarketPrice] = useState<string | null>(null);
|
||||||
const variables = useMemo(
|
const variables = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -17,8 +17,8 @@ import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
|
|||||||
import { HeaderStat } from '../header';
|
import { HeaderStat } from '../header';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
marketId: string;
|
marketId?: string;
|
||||||
onSelect: (marketId: string) => void;
|
onSelect?: (marketId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TradingModeMarket = Omit<DealTicketMarketFragment, 'depth'>;
|
type TradingModeMarket = Omit<DealTicketMarketFragment, 'depth'>;
|
||||||
@ -63,6 +63,13 @@ export const MarketTradingModeComponent = ({ marketId, onSelect }: Props) => {
|
|||||||
skip: !marketId || !data,
|
skip: !marketId || !data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const content =
|
||||||
|
tradingMode === Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||||
|
trigger &&
|
||||||
|
trigger !== Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
||||||
|
? `${MarketTradingModeMapping[tradingMode]} - ${AuctionTriggerMapping[trigger]}`
|
||||||
|
: MarketTradingModeMapping[tradingMode as Types.MarketTradingMode];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderStat
|
<HeaderStat
|
||||||
heading={t('Trading mode')}
|
heading={t('Trading mode')}
|
||||||
@ -77,14 +84,7 @@ export const MarketTradingModeComponent = ({ marketId, onSelect }: Props) => {
|
|||||||
}
|
}
|
||||||
testId="market-trading-mode"
|
testId="market-trading-mode"
|
||||||
>
|
>
|
||||||
<div>
|
<div>{content || '-'}</div>
|
||||||
{tradingMode ===
|
|
||||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
|
||||||
trigger &&
|
|
||||||
trigger !== Schema.AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED
|
|
||||||
? `${MarketTradingModeMapping[tradingMode]} - ${AuctionTriggerMapping[trigger]}`
|
|
||||||
: MarketTradingModeMapping[tradingMode as Types.MarketTradingMode]}
|
|
||||||
</div>
|
|
||||||
</HeaderStat>
|
</HeaderStat>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import { columnHeaders } from './select-market-columns';
|
import { columnHeaders } from './select-market-columns';
|
||||||
|
import classNames from 'classnames';
|
||||||
import type { Column } from './select-market-columns';
|
import type { Column } from './select-market-columns';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
export const SelectMarketTableHeader = ({
|
export const SelectMarketTableHeader = ({
|
||||||
detailed = false,
|
detailed = false,
|
||||||
@ -63,3 +62,23 @@ export const SelectMarketTableRow = ({
|
|||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SelectMarketTableRowSplash = ({
|
||||||
|
children,
|
||||||
|
colSpan,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
colSpan: number;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<tr className={`relative`}>
|
||||||
|
<td
|
||||||
|
className="text-center p-10 pt-14 text-xs"
|
||||||
|
key="splash"
|
||||||
|
colSpan={colSpan}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -3,6 +3,7 @@ import { positionsDataProvider } from '@vegaprotocol/positions';
|
|||||||
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
|
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
|
ExternalLink,
|
||||||
Icon,
|
Icon,
|
||||||
Intent,
|
Intent,
|
||||||
Link as UILink,
|
Link as UILink,
|
||||||
@ -21,6 +22,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
SelectMarketTableHeader,
|
SelectMarketTableHeader,
|
||||||
SelectMarketTableRow,
|
SelectMarketTableRow,
|
||||||
|
SelectMarketTableRowSplash,
|
||||||
} from './select-market-table';
|
} from './select-market-table';
|
||||||
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
@ -31,6 +33,9 @@ import type {
|
|||||||
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';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useLinks } from '../../lib/use-links';
|
||||||
|
import { NEW_PROPOSAL_LINK } from '../constants';
|
||||||
|
|
||||||
type Market = MarketWithCandles & MarketWithData;
|
type Market = MarketWithCandles & MarketWithData;
|
||||||
|
|
||||||
export const SelectMarketLandingTable = ({
|
export const SelectMarketLandingTable = ({
|
||||||
@ -90,6 +95,7 @@ export const SelectAllMarketsTableBody = ({
|
|||||||
headers?: Column[];
|
headers?: Column[];
|
||||||
tableColumns?: (market: Market, openVolume?: string) => Column[];
|
tableColumns?: (market: Market, openVolume?: string) => Column[];
|
||||||
}) => {
|
}) => {
|
||||||
|
const tokenLink = useLinks('token');
|
||||||
if (!markets) return null;
|
if (!markets) return null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -98,19 +104,28 @@ export const SelectAllMarketsTableBody = ({
|
|||||||
</thead>
|
</thead>
|
||||||
{/* Border styles required to create space between tbody elements margin/padding don't work */}
|
{/* Border styles required to create space between tbody elements margin/padding don't work */}
|
||||||
<tbody className="border-b-[10px] border-transparent">
|
<tbody className="border-b-[10px] border-transparent">
|
||||||
{markets?.map((market, i) => (
|
{markets.length > 0 ? (
|
||||||
<SelectMarketTableRow
|
markets?.map((market, i) => (
|
||||||
marketId={market.id}
|
<SelectMarketTableRow
|
||||||
key={i}
|
marketId={market.id}
|
||||||
detailed={true}
|
key={i}
|
||||||
onSelect={onSelect}
|
detailed={true}
|
||||||
columns={tableColumns(
|
onSelect={onSelect}
|
||||||
market,
|
columns={tableColumns(
|
||||||
positions &&
|
market,
|
||||||
positions.find((p) => p.market.id === market.id)?.openVolume
|
positions &&
|
||||||
)}
|
positions.find((p) => p.market.id === market.id)?.openVolume
|
||||||
/>
|
)}
|
||||||
))}
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectMarketTableRowSplash colSpan={12}>
|
||||||
|
{t('No markets ')}
|
||||||
|
<ExternalLink href={tokenLink(NEW_PROPOSAL_LINK)}>
|
||||||
|
{t('Propose a new market')}
|
||||||
|
</ExternalLink>
|
||||||
|
</SelectMarketTableRowSplash>
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -172,7 +187,7 @@ export const SelectMarketPopover = ({
|
|||||||
{marketsLoading || (pubKey && positionsLoading) ? (
|
{marketsLoading || (pubKey && positionsLoading) ? (
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Loader size="small" />
|
<Loader size="small" />
|
||||||
Loading market data
|
{t('Loading market data')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<table className="relative text-sm w-full whitespace-nowrap">
|
<table className="relative text-sm w-full whitespace-nowrap">
|
||||||
|
1
apps/trading/components/welcome-notice/index.ts
Normal file
1
apps/trading/components/welcome-notice/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './welcome-notice-dialog';
|
134
apps/trading/components/welcome-notice/welcome-notice-dialog.tsx
Normal file
134
apps/trading/components/welcome-notice/welcome-notice-dialog.tsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { Dialog, ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import { proposalsListDataProvider } from '@vegaprotocol/governance';
|
||||||
|
import { Schema as Types } from '@vegaprotocol/types';
|
||||||
|
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
import { useGlobalStore } from '../../stores';
|
||||||
|
import take from 'lodash/take';
|
||||||
|
import { useLinks } from '../../lib/use-links';
|
||||||
|
import { GOVERNANCE_LINK, NEW_PROPOSAL_LINK } from '../constants';
|
||||||
|
import { activeMarketsProvider } from '@vegaprotocol/market-list';
|
||||||
|
|
||||||
|
export const WelcomeNoticeDialog = () => {
|
||||||
|
const [welcomeNoticeDialog, update] = useGlobalStore((store) => [
|
||||||
|
store.welcomeNoticeDialog,
|
||||||
|
store.update,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onOpenChange = useCallback(
|
||||||
|
(isOpen: boolean) => {
|
||||||
|
update({ welcomeNoticeDialog: isOpen });
|
||||||
|
},
|
||||||
|
[update]
|
||||||
|
);
|
||||||
|
|
||||||
|
const variables = useMemo(() => {
|
||||||
|
return {
|
||||||
|
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { data } = useDataProvider({
|
||||||
|
dataProvider: proposalsListDataProvider,
|
||||||
|
variables,
|
||||||
|
skipUpdates: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newMarkets = take(
|
||||||
|
(data || []).filter((proposal) =>
|
||||||
|
[
|
||||||
|
Types.ProposalState.STATE_OPEN,
|
||||||
|
Types.ProposalState.STATE_PASSED,
|
||||||
|
Types.ProposalState.STATE_WAITING_FOR_NODE_VOTE,
|
||||||
|
].includes(proposal.state)
|
||||||
|
),
|
||||||
|
3
|
||||||
|
).map((proposal) => ({
|
||||||
|
id: proposal.id,
|
||||||
|
displayName:
|
||||||
|
proposal.terms.change.__typename === 'NewMarket' &&
|
||||||
|
proposal.terms.change.instrument.code,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const tokenLink = useLinks('token');
|
||||||
|
const consoleFairgroundLink = useLinks('console-fairground');
|
||||||
|
|
||||||
|
const proposedMarkets = useMemo(
|
||||||
|
() =>
|
||||||
|
newMarkets.length > 0 && (
|
||||||
|
<div className="mt-7 pt-8 border-t border-neutral-700">
|
||||||
|
<h2 className="font-alpha uppercase text-2xl">
|
||||||
|
{t('Proposed markets')}
|
||||||
|
</h2>
|
||||||
|
<dl data-testid="welcome-notice-proposed-markets" className="py-5">
|
||||||
|
{newMarkets.map(({ displayName, id }, i) => (
|
||||||
|
<div className="pt-1 flex justify-between" key={i}>
|
||||||
|
<dl>{displayName}</dl>
|
||||||
|
<dt>
|
||||||
|
<ExternalLink href={tokenLink(`${GOVERNANCE_LINK}/${id}`)}>
|
||||||
|
{t('View or vote')}
|
||||||
|
</ExternalLink>
|
||||||
|
</dt>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
<ExternalLink href={tokenLink(GOVERNANCE_LINK)}>
|
||||||
|
{t('View all proposed markets')}
|
||||||
|
</ExternalLink>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
[newMarkets, tokenLink]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={welcomeNoticeDialog} onChange={onOpenChange}>
|
||||||
|
<h1
|
||||||
|
data-testid="welcome-notice-title"
|
||||||
|
className="font-alpha uppercase text-4xl mb-7 mt-5"
|
||||||
|
>
|
||||||
|
{t('Welcome to Console')}
|
||||||
|
</h1>
|
||||||
|
<p className="leading-6 mb-7">
|
||||||
|
{t(
|
||||||
|
'Vega mainnet is now live, but markets need to be voted for before the can be traded on. In the meantime:'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<ul className="list-[square] pl-7">
|
||||||
|
<li>
|
||||||
|
<ExternalLink target="_blank" href={consoleFairgroundLink()}>
|
||||||
|
{t('Try out Console')}
|
||||||
|
</ExternalLink>
|
||||||
|
{t(' on Fairground, our Testnet')}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ExternalLink target="_blank" href={tokenLink(GOVERNANCE_LINK)}>
|
||||||
|
{t('View and vote for proposed markets')}
|
||||||
|
</ExternalLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ExternalLink target="_blank" href={tokenLink(NEW_PROPOSAL_LINK)}>
|
||||||
|
{t('Propose your own markets')}
|
||||||
|
</ExternalLink>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ExternalLink target="_blank" href={tokenLink()}>
|
||||||
|
{t('Read about the mainnet launch')}
|
||||||
|
</ExternalLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{proposedMarkets}
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useWelcomeNoticeDialog = () => {
|
||||||
|
const { update } = useGlobalStore((store) => ({ update: store.update }));
|
||||||
|
const { data } = useDataProvider({
|
||||||
|
dataProvider: activeMarketsProvider,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.length === 0) {
|
||||||
|
update({ welcomeNoticeDialog: true });
|
||||||
|
}
|
||||||
|
}, [data, update]);
|
||||||
|
};
|
24
apps/trading/lib/use-links.ts
Normal file
24
apps/trading/lib/use-links.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import trim from 'lodash/trim';
|
||||||
|
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
type DApp = 'console' | 'console-fairground' | 'token' | 'explorer';
|
||||||
|
export const useLinks = (dapp: DApp) => {
|
||||||
|
const { VEGA_ENV, VEGA_NETWORKS, VEGA_TOKEN_URL, VEGA_EXPLORER_URL } =
|
||||||
|
useEnvironment();
|
||||||
|
|
||||||
|
const urls: { [k in DApp]: string } = {
|
||||||
|
console: (VEGA_NETWORKS && VEGA_NETWORKS[VEGA_ENV]) || '',
|
||||||
|
'console-fairground':
|
||||||
|
(VEGA_NETWORKS && VEGA_NETWORKS[Networks.TESTNET]) || '',
|
||||||
|
token: VEGA_TOKEN_URL || '',
|
||||||
|
explorer: VEGA_EXPLORER_URL || '',
|
||||||
|
};
|
||||||
|
const baseUrl = trim(urls[dapp], '/');
|
||||||
|
|
||||||
|
const link = useCallback(
|
||||||
|
(url?: string) => `${baseUrl}/${trim(url, '/') || ''}`,
|
||||||
|
[baseUrl]
|
||||||
|
);
|
||||||
|
return link;
|
||||||
|
};
|
@ -49,6 +49,19 @@ function AppBody({ Component }: AppProps) {
|
|||||||
<ThemeContext.Provider value={theme}>
|
<ThemeContext.Provider value={theme}>
|
||||||
<Head>
|
<Head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
href="https://static.vega.xyz/AlphaLyrae-Medium.woff2"
|
||||||
|
as="font"
|
||||||
|
type="font/woff2"
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/x-icon"
|
||||||
|
href="https://static.vega.xyz/favicon.ico"
|
||||||
|
/>
|
||||||
|
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
||||||
</Head>
|
</Head>
|
||||||
<Title />
|
<Title />
|
||||||
<div className="h-full relative dark:bg-black dark:text-white z-0 grid grid-rows-[min-content,1fr,min-content]">
|
<div className="h-full relative dark:bg-black dark:text-white z-0 grid grid-rows-[min-content,1fr,min-content]">
|
||||||
|
@ -9,6 +9,7 @@ import { WithdrawalDialog } from '@vegaprotocol/withdraws';
|
|||||||
import { DepositDialog } from '@vegaprotocol/deposits';
|
import { DepositDialog } from '@vegaprotocol/deposits';
|
||||||
import { Web3Container } from '@vegaprotocol/web3';
|
import { Web3Container } from '@vegaprotocol/web3';
|
||||||
import { Web3ConnectUncontrolledDialog } from '@vegaprotocol/web3';
|
import { Web3ConnectUncontrolledDialog } from '@vegaprotocol/web3';
|
||||||
|
import { WelcomeNoticeDialog } from '../components/welcome-notice';
|
||||||
|
|
||||||
const DialogsContainer = () => {
|
const DialogsContainer = () => {
|
||||||
const { isOpen, symbol, trigger, setOpen } = useAssetDetailsDialogStore();
|
const { isOpen, symbol, trigger, setOpen } = useAssetDetailsDialogStore();
|
||||||
@ -27,6 +28,7 @@ const DialogsContainer = () => {
|
|||||||
<Web3Container childrenOnly connectEagerly>
|
<Web3Container childrenOnly connectEagerly>
|
||||||
<WithdrawalDialog />
|
<WithdrawalDialog />
|
||||||
</Web3Container>
|
</Web3Container>
|
||||||
|
<WelcomeNoticeDialog />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ interface GlobalStore {
|
|||||||
landingDialog: boolean;
|
landingDialog: boolean;
|
||||||
riskNoticeDialog: boolean;
|
riskNoticeDialog: boolean;
|
||||||
marketId: string | null;
|
marketId: string | null;
|
||||||
|
welcomeNoticeDialog: boolean;
|
||||||
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
|
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
|
|||||||
landingDialog: false,
|
landingDialog: false,
|
||||||
riskNoticeDialog: false,
|
riskNoticeDialog: false,
|
||||||
marketId: LocalStorage.getItem('marketId') || null,
|
marketId: LocalStorage.getItem('marketId') || null,
|
||||||
|
welcomeNoticeDialog: false,
|
||||||
update: (state) => {
|
update: (state) => {
|
||||||
set(state);
|
set(state);
|
||||||
if (state.marketId) {
|
if (state.marketId) {
|
||||||
|
@ -49,7 +49,7 @@ export const calcCandleVolume = (
|
|||||||
|
|
||||||
export interface MarketInfoContainerProps {
|
export interface MarketInfoContainerProps {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
onSelect: (id: string) => void;
|
onSelect?: (id: string) => void;
|
||||||
}
|
}
|
||||||
export const MarketInfoContainer = ({
|
export const MarketInfoContainer = ({
|
||||||
marketId,
|
marketId,
|
||||||
@ -77,7 +77,7 @@ export const MarketInfoContainer = ({
|
|||||||
return (
|
return (
|
||||||
<AsyncRenderer data={data} loading={loading} error={error}>
|
<AsyncRenderer data={data} loading={loading} error={error}>
|
||||||
{data && data.market ? (
|
{data && data.market ? (
|
||||||
<Info market={data.market} onSelect={onSelect} />
|
<Info market={data.market} onSelect={(id) => onSelect?.(id)} />
|
||||||
) : (
|
) : (
|
||||||
<Splash>
|
<Splash>
|
||||||
<p>{t('Could not load market')}</p>
|
<p>{t('Could not load market')}</p>
|
||||||
|
@ -11,7 +11,7 @@ interface AsyncRendererProps<T> {
|
|||||||
noDataMessage?: string;
|
noDataMessage?: string;
|
||||||
children?: ReactNode | null;
|
children?: ReactNode | null;
|
||||||
render?: (data: T) => ReactNode;
|
render?: (data: T) => ReactNode;
|
||||||
noDataCondition?(data: T): boolean;
|
noDataCondition?(data?: T): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AsyncRenderer<T = object>({
|
export function AsyncRenderer<T = object>({
|
||||||
@ -39,9 +39,9 @@ export function AsyncRenderer<T = object>({
|
|||||||
return <Splash>{loadingMessage ? loadingMessage : t('Loading...')}</Splash>;
|
return <Splash>{loadingMessage ? loadingMessage : t('Loading...')}</Splash>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data || (noDataCondition && noDataCondition(data))) {
|
if (noDataCondition ? noDataCondition(data) : !data) {
|
||||||
return <Splash>{noDataMessage ? noDataMessage : t('No data')}</Splash>;
|
return <Splash>{noDataMessage ? noDataMessage : t('No data')}</Splash>;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
return <>{render ? render(data) : children}</>;
|
return <>{render ? render(data as T) : children}</>;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user