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 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'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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]);
|
||||
|
@ -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 (
|
||||
|
@ -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 (
|
||||
|
@ -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]
|
||||
|
@ -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';
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
() => ({
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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">
|
||||
|
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}>
|
||||
<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]">
|
||||
|
@ -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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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}</>;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user