feat: no markets (#2097)

* feat: no markets

* feat: no markets

* feat: no markets

* feat: no markets

* feat: no markets
This commit is contained in:
Art 2022-11-18 18:08:48 +01:00 committed by GitHub
parent 98a3d2267b
commit 8e5012891c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 384 additions and 79 deletions

View File

@ -1,7 +1,53 @@
import { aliasQuery } from '@vegaprotocol/cypress';
import type { ProposalListFieldsFragment } from '@vegaprotocol/governance';
import { Schema } from '@vegaprotocol/types';
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' }, () => {
beforeEach(() => {
cy.mockTradingPage();
@ -51,9 +97,8 @@ describe('home', { tags: '@regression' }, () => {
});
});
describe('no default found', () => {
it('redirects to a the market list page if no sensible default is found', () => {
// Mock markets query that is triggered by home page to find default market
describe('no markets found', () => {
it('redirects to a the empty market page and displays welcome notice', () => {
cy.mockGQL((req) => {
const data = {
marketsConnection: {
@ -61,13 +106,30 @@ describe('home', { tags: '@regression' }, () => {
edges: [],
},
};
const proposalA: ProposalListFieldsFragment =
generateProposal('AAAZZZ');
aliasQuery(req, 'Markets', data);
aliasQuery(req, 'MarketsData', data);
aliasQuery(req, 'ProposalsList', {
proposalsConnection: {
__typename: 'ProposalsConnection',
edges: [{ __typename: 'ProposalEdge', node: proposalA }],
},
});
});
cy.visit('/');
cy.wait('@Markets');
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'
);
});
});
});

View File

@ -5,6 +5,7 @@ import {
useDataProvider,
} from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { EMPTY_MARKET_ID } from '../../components/constants';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useGlobalStore, usePageTitleStore } from '../../stores';
@ -27,9 +28,8 @@ export const Home = () => {
}));
useEffect(() => {
update({ landingDialog: true });
if (data) {
update({ landingDialog: data.length > 0 });
const marketId = data[0]?.id;
const marketName = data[0]?.tradableInstrument.instrument.name;
const marketPrice = data[0]?.data?.markPrice
@ -46,10 +46,8 @@ export const Home = () => {
if (pageTitle !== newPageTitle) {
updateTitle(newPageTitle);
}
}
// Fallback to the markets list page
else {
navigate('/markets');
} else {
navigate(`/markets/${EMPTY_MARKET_ID}`);
}
}
}, [data, navigate, riskNoticeDialog, update, pageTitle, updateTitle]);

View File

@ -14,11 +14,14 @@ import type {
Candle,
MarketDataUpdateFieldsFragment,
} from '@vegaprotocol/market-list';
import { marketProvider, marketDataProvider } from '@vegaprotocol/market-list';
import { useGlobalStore, usePageTitleStore } from '../../stores';
import { TradeGrid, TradePanels } from './trade-grid';
import { ColumnKind, SelectMarketDialog } from '../../components/select-market';
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) => {
return markPrice && decimalPlaces
@ -40,7 +43,9 @@ export const Market = ({
}) => {
const params = useParams();
const navigate = useNavigate();
const marketId = params.marketId;
const isEmpty = params.marketId === EMPTY_MARKET_ID;
const marketId = isEmpty ? undefined : params.marketId;
const { w } = useWindowSize();
const { landingDialog, riskNoticeDialog, update } = useGlobalStore(
(store) => ({
@ -108,17 +113,16 @@ export const Market = ({
skip: !marketId || !data,
});
useWelcomeNoticeDialog();
const tradeView = useMemo(() => {
if (!data) {
return null;
}
if (w > 960) {
return <TradeGrid market={data} onSelect={onSelect} />;
}
return <TradePanels market={data} onSelect={onSelect} />;
}, [w, data, onSelect]);
if (!marketId) {
if (!marketId && !isEmpty) {
return (
<Splash>
<p>{t('Not found')}</p>
@ -131,8 +135,9 @@ export const Market = ({
loading={loading}
error={error}
data={data || undefined}
noDataCondition={(data) => false}
render={(data) => {
if (!data) {
if (!data && !isEmpty) {
return <Splash>{t('Market not found')}</Splash>;
}
return (

View File

@ -9,7 +9,7 @@ import { LayoutPriority } from 'allotment';
import classNames from 'classnames';
import AutoSizer from 'react-virtualized-auto-sizer';
import { memo, useState } from 'react';
import type { ReactNode } from 'react';
import type { ReactNode, ComponentProps } from 'react';
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import {
@ -19,6 +19,7 @@ import {
ResizableGridPanel,
ButtonLink,
Link,
Splash,
} from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/react-helpers';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
@ -36,13 +37,32 @@ import { MarketMarkPrice } from '../../components/market-mark-price';
import { MarketTradingModeComponent } from '../../components/market-trading-mode';
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 = {
Candles: CandlesChartContainer,
Depth: DepthChartContainer,
Ticket: DealTicketContainer,
Info: MarketInfoContainer,
Orderbook: OrderbookContainer,
Trades: TradesContainer,
Candles: requiresMarket(CandlesChartContainer),
Depth: requiresMarket(DepthChartContainer),
Ticket: requiresMarket(DealTicketContainer),
Info: requiresMarket(MarketInfoContainer),
Orderbook: requiresMarket(OrderbookContainer),
Trades: requiresMarket(TradesContainer),
Positions: PositionsContainer,
Orders: OrderListContainer,
Collateral: AccountsContainer,
@ -52,11 +72,11 @@ const TradingViews = {
type TradingView = keyof typeof TradingViews;
type ExpiryLabelProps = {
market: SingleMarketFieldsFragment;
market: SingleMarketFieldsFragment | null;
};
const ExpiryLabel = ({ market }: ExpiryLabelProps) => {
const content = getExpiryDate(market);
const content = market ? getExpiryDate(market) : '-';
return <div data-testid="trading-expiry">{content}</div>;
};
@ -69,7 +89,7 @@ const ExpiryTooltipContent = ({
market,
explorerUrl,
}: ExpiryTooltipContentProps) => {
if (market.marketTimestamps.close === null) {
if (market?.marketTimestamps.close === null) {
const oracleId =
market.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination?.id;
@ -94,7 +114,7 @@ const ExpiryTooltipContent = ({
};
interface TradeMarketHeaderProps {
market: SingleMarketFieldsFragment;
market: SingleMarketFieldsFragment | null;
onSelect: (marketId: string) => void;
}
@ -106,7 +126,7 @@ export const TradeMarketHeader = ({
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const symbol =
market.tradableInstrument.instrument.product?.settlementAsset?.symbol;
market?.tradableInstrument.instrument.product?.settlementAsset?.symbol;
const onCellClick: OnCellClickHandler = (e, kind, value) => {
if (value && kind === ColumnKind.Asset) {
@ -118,7 +138,7 @@ export const TradeMarketHeader = ({
<Header
title={
<SelectMarketPopover
marketName={market.tradableInstrument.instrument.name}
marketName={market?.tradableInstrument.instrument.name || NO_MARKET}
onSelect={onSelect}
onCellClick={onCellClick}
/>
@ -127,19 +147,21 @@ export const TradeMarketHeader = ({
<HeaderStat
heading={t('Expiry')}
description={
<ExpiryTooltipContent
market={market}
explorerUrl={VEGA_EXPLORER_URL}
/>
market && (
<ExpiryTooltipContent
market={market}
explorerUrl={VEGA_EXPLORER_URL}
/>
)
}
testId="market-expiry"
>
<ExpiryLabel market={market} />
</HeaderStat>
<MarketMarkPrice marketId={market.id} />
<Last24hPriceChange marketId={market.id} />
<Last24hVolume marketId={market.id} />
<MarketTradingModeComponent marketId={market.id} onSelect={onSelect} />
<MarketMarkPrice marketId={market?.id} />
<Last24hPriceChange marketId={market?.id} />
<Last24hVolume marketId={market?.id} />
<MarketTradingModeComponent marketId={market?.id} onSelect={onSelect} />
{symbol ? (
<HeaderStat
heading={t('Settlement asset')}
@ -161,7 +183,7 @@ export const TradeMarketHeader = ({
};
interface TradeGridProps {
market: SingleMarketFieldsFragment;
market: SingleMarketFieldsFragment | null;
onSelect: (marketId: string) => void;
}
@ -170,7 +192,7 @@ const MainGrid = ({
onSelect,
}: {
marketId: string;
onSelect: (marketId: string) => void;
onSelect?: (marketId: string) => void;
}) => (
<ResizableGrid vertical>
<ResizableGridPanel minSize={75} priority={LayoutPriority.High}>
@ -205,7 +227,7 @@ const MainGrid = ({
<TradingViews.Info
marketId={marketId}
onSelect={(id: string) => {
onSelect(id);
onSelect?.(id);
}}
/>
</Tab>
@ -260,7 +282,7 @@ export const TradeGrid = ({ market, onSelect }: TradeGridProps) => {
return (
<div className="h-full grid grid-rows-[min-content_1fr]">
<TradeMarketHeader market={market} onSelect={onSelect} />
<MainGridWrapped marketId={market.id} onSelect={onSelect} />
<MainGridWrapped marketId={market?.id || ''} onSelect={onSelect} />
</div>
);
};
@ -280,7 +302,7 @@ const TradeGridChild = ({ children }: TradeGridChildProps) => {
};
interface TradePanelsProps {
market: SingleMarketFieldsFragment;
market: SingleMarketFieldsFragment | null;
onSelect: (marketId: string) => void;
}
@ -297,7 +319,9 @@ export const TradePanels = ({ market, onSelect }: TradePanelsProps) => {
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 (

View File

@ -2,13 +2,15 @@ import { useCallback } from 'react';
import { MarketsContainer } from '@vegaprotocol/market-list';
import { useGlobalStore } from '../../stores';
import { useNavigate } from 'react-router-dom';
import { useWelcomeNoticeDialog } from '../../components/welcome-notice';
export const Markets = () => {
const navigate = useNavigate();
const { update } = useGlobalStore((store) => ({ update: store.update }));
useWelcomeNoticeDialog();
const handleOnSelect = useCallback(
(marketId: string) => {
update({ marketId });
update({ marketId, welcomeNoticeDialog: false });
navigate(`/markets/${marketId}`);
},
[update, navigate]

View File

@ -1,3 +1,7 @@
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 GOVERNANCE_LINK = '/governance';
export const NEW_PROPOSAL_LINK = '/governance/propose/new-market';

View File

@ -15,7 +15,7 @@ import {
import { HeaderStat } from '../header';
import * as constants from '../constants';
export const Last24hPriceChange = ({ marketId }: { marketId: string }) => {
export const Last24hPriceChange = ({ marketId }: { marketId?: string }) => {
const [candlesClose, setCandlesClose] = useState<string[]>([]);
const yesterday = useYesterday();
// Cache timestamp for yesterday to prevent full unmount of market page when

View File

@ -20,7 +20,7 @@ import type {
SingleMarketFieldsFragment,
Candle,
} from '@vegaprotocol/market-list';
export const Last24hVolume = ({ marketId }: { marketId: string }) => {
export const Last24hVolume = ({ marketId }: { marketId?: string }) => {
const [candleVolume, setCandleVolume] = useState<string>();
const yesterday = useYesterday();
// Cache timestamp for yesterday to prevent full unmount of market page when

View File

@ -14,7 +14,7 @@ import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
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 variables = useMemo(
() => ({

View File

@ -17,8 +17,8 @@ import { marketDataProvider, marketProvider } from '@vegaprotocol/market-list';
import { HeaderStat } from '../header';
interface Props {
marketId: string;
onSelect: (marketId: string) => void;
marketId?: string;
onSelect?: (marketId: string) => void;
}
type TradingModeMarket = Omit<DealTicketMarketFragment, 'depth'>;
@ -63,6 +63,13 @@ export const MarketTradingModeComponent = ({ marketId, onSelect }: Props) => {
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 (
<HeaderStat
heading={t('Trading mode')}
@ -77,14 +84,7 @@ export const MarketTradingModeComponent = ({ marketId, onSelect }: Props) => {
}
testId="market-trading-mode"
>
<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>
<div>{content || '-'}</div>
</HeaderStat>
);
};

View File

@ -1,8 +1,7 @@
import classNames from 'classnames';
import { columnHeaders } from './select-market-columns';
import classNames from 'classnames';
import type { Column } from './select-market-columns';
import type { ReactNode } from 'react';
export const SelectMarketTableHeader = ({
detailed = false,
@ -63,3 +62,23 @@ export const SelectMarketTableRow = ({
</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>
);
};

View File

@ -3,6 +3,7 @@ import { positionsDataProvider } from '@vegaprotocol/positions';
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import {
Dialog,
ExternalLink,
Icon,
Intent,
Link as UILink,
@ -21,6 +22,7 @@ import {
import {
SelectMarketTableHeader,
SelectMarketTableRow,
SelectMarketTableRowSplash,
} from './select-market-table';
import type { ReactNode } from 'react';
@ -31,6 +33,9 @@ import type {
import type { PositionFieldsFragment } from '@vegaprotocol/positions';
import type { Column, OnCellClickHandler } from './select-market-columns';
import { Link } from 'react-router-dom';
import { useLinks } from '../../lib/use-links';
import { NEW_PROPOSAL_LINK } from '../constants';
type Market = MarketWithCandles & MarketWithData;
export const SelectMarketLandingTable = ({
@ -90,6 +95,7 @@ export const SelectAllMarketsTableBody = ({
headers?: Column[];
tableColumns?: (market: Market, openVolume?: string) => Column[];
}) => {
const tokenLink = useLinks('token');
if (!markets) return null;
return (
<>
@ -98,19 +104,28 @@ export const SelectAllMarketsTableBody = ({
</thead>
{/* Border styles required to create space between tbody elements margin/padding don't work */}
<tbody className="border-b-[10px] border-transparent">
{markets?.map((market, i) => (
<SelectMarketTableRow
marketId={market.id}
key={i}
detailed={true}
onSelect={onSelect}
columns={tableColumns(
market,
positions &&
positions.find((p) => p.market.id === market.id)?.openVolume
)}
/>
))}
{markets.length > 0 ? (
markets?.map((market, i) => (
<SelectMarketTableRow
marketId={market.id}
key={i}
detailed={true}
onSelect={onSelect}
columns={tableColumns(
market,
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>
</>
);
@ -172,7 +187,7 @@ export const SelectMarketPopover = ({
{marketsLoading || (pubKey && positionsLoading) ? (
<div className="flex items-center gap-4">
<Loader size="small" />
Loading market data
{t('Loading market data')}
</div>
) : (
<table className="relative text-sm w-full whitespace-nowrap">

View File

@ -0,0 +1 @@
export * from './welcome-notice-dialog';

View 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]);
};

View 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;
};

View File

@ -49,6 +49,19 @@ function AppBody({ Component }: AppProps) {
<ThemeContext.Provider value={theme}>
<Head>
<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>
<Title />
<div className="h-full relative dark:bg-black dark:text-white z-0 grid grid-rows-[min-content,1fr,min-content]">

View File

@ -9,6 +9,7 @@ import { WithdrawalDialog } from '@vegaprotocol/withdraws';
import { DepositDialog } from '@vegaprotocol/deposits';
import { Web3Container } from '@vegaprotocol/web3';
import { Web3ConnectUncontrolledDialog } from '@vegaprotocol/web3';
import { WelcomeNoticeDialog } from '../components/welcome-notice';
const DialogsContainer = () => {
const { isOpen, symbol, trigger, setOpen } = useAssetDetailsDialogStore();
@ -27,6 +28,7 @@ const DialogsContainer = () => {
<Web3Container childrenOnly connectEagerly>
<WithdrawalDialog />
</Web3Container>
<WelcomeNoticeDialog />
</>
);
};

View File

@ -6,6 +6,7 @@ interface GlobalStore {
landingDialog: boolean;
riskNoticeDialog: boolean;
marketId: string | null;
welcomeNoticeDialog: boolean;
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
}
@ -19,6 +20,7 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
landingDialog: false,
riskNoticeDialog: false,
marketId: LocalStorage.getItem('marketId') || null,
welcomeNoticeDialog: false,
update: (state) => {
set(state);
if (state.marketId) {

View File

@ -49,7 +49,7 @@ export const calcCandleVolume = (
export interface MarketInfoContainerProps {
marketId: string;
onSelect: (id: string) => void;
onSelect?: (id: string) => void;
}
export const MarketInfoContainer = ({
marketId,
@ -77,7 +77,7 @@ export const MarketInfoContainer = ({
return (
<AsyncRenderer data={data} loading={loading} error={error}>
{data && data.market ? (
<Info market={data.market} onSelect={onSelect} />
<Info market={data.market} onSelect={(id) => onSelect?.(id)} />
) : (
<Splash>
<p>{t('Could not load market')}</p>

View File

@ -11,7 +11,7 @@ interface AsyncRendererProps<T> {
noDataMessage?: string;
children?: ReactNode | null;
render?: (data: T) => ReactNode;
noDataCondition?(data: T): boolean;
noDataCondition?(data?: T): boolean;
}
export function AsyncRenderer<T = object>({
@ -39,9 +39,9 @@ export function AsyncRenderer<T = object>({
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>;
}
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{render ? render(data) : children}</>;
return <>{render ? render(data as T) : children}</>;
}