From b1f872f54cc7395b94497fdce52eab79efc81e4e Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Mon, 4 May 2026 17:56:34 -0700 Subject: [PATCH] fix(D8): make empty-state chips market-aware --- web/src/views/HomeView.dom.test.tsx | 23 +++++++++ web/src/views/HomeView.tsx | 73 +++++++++++++++++++++++++---- 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/web/src/views/HomeView.dom.test.tsx b/web/src/views/HomeView.dom.test.tsx index 1e94573..7b3c08b 100644 --- a/web/src/views/HomeView.dom.test.tsx +++ b/web/src/views/HomeView.dom.test.tsx @@ -160,4 +160,27 @@ describe('TickerHeader', () => { await waitFor(() => expect(screen.getByText('50.0')).toBeInTheDocument()); expect(screen.getAllByText('100.00').length).toBeGreaterThanOrEqual(2); }); + + it('uses configured crypto symbols for empty-state suggestions', async () => { + const user = userEvent.setup(); + const setActiveSymbol = vi.fn(); + const cryptoContext: AppContextValue = { + ...appContext, + activeSymbol: '', + setActiveSymbol, + profile: { symbols: 'BTC/USDT, ETH/USDT', market_type: 'crypto' }, + botState: { + ...appContext.botState, + symbols: {}, + } as any, + }; + + renderTickerHeader(cryptoContext); + + expect(screen.getByRole('button', { name: 'BTC/USDT' })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'AAPL' })).not.toBeInTheDocument(); + + await user.click(screen.getByRole('button', { name: 'ETH/USDT' })); + expect(setActiveSymbol).toHaveBeenCalledWith('ETH/USDT'); + }); }); diff --git a/web/src/views/HomeView.tsx b/web/src/views/HomeView.tsx index da2f72b..990271c 100644 --- a/web/src/views/HomeView.tsx +++ b/web/src/views/HomeView.tsx @@ -41,6 +41,41 @@ interface ResearchProfile { exchangeShortName?: string; } +const EQUITY_EMPTY_STATE_SYMBOLS = ['AAPL','MSFT','GOOGL','AMZN','NVDA']; +const CRYPTO_EMPTY_STATE_SYMBOLS = ['BTC/USDT','ETH/USDT','SOL/USDT','ADA/USDT','DOGE/USDT']; +const CRYPTO_BASES = new Set(['BTC','ETH','SOL','ADA','DOGE','XRP','LTC','DOT','AVAX','MATIC','BNB']); + +function uniqueSymbols(symbols: string[]) { + return Array.from(new Set(symbols.map(s => s.trim().toUpperCase()).filter(Boolean))); +} + +function splitSymbols(raw: unknown) { + if (Array.isArray(raw)) return uniqueSymbols(raw.map(String)); + if (typeof raw === 'string') return uniqueSymbols(raw.split(',')); + return []; +} + +function isCryptoLikeSymbol(symbol: string) { + const normalized = symbol.trim().toUpperCase(); + const base = normalized.split(/[/-]/)[0]; + return normalized.includes('/') || normalized.endsWith('USDT') || CRYPTO_BASES.has(base); +} + +function emptyStateSuggestions(profile: any, botSymbols: Record) { + const configuredSymbols = uniqueSymbols([ + ...splitSymbols(profile?.symbols), + ...Object.keys(botSymbols ?? {}), + ]); + if (configuredSymbols.length > 0) return configuredSymbols.slice(0, 5); + + const marketHint = String( + profile?.market_type ?? profile?.marketType ?? profile?.asset_class ?? profile?.assetClass ?? profile?.exchange ?? '', + ).toLowerCase(); + if (marketHint.includes('crypto')) return CRYPTO_EMPTY_STATE_SYMBOLS; + + return EQUITY_EMPTY_STATE_SYMBOLS; +} + const INDICATORS: Array<{ key: IndicatorKey; label: string; hint: string }> = [ { key: 'rsi', label: 'RSI', hint: '14-period momentum' }, { key: 'macd', label: 'MACD', hint: '12/26 EMA trend' }, @@ -751,7 +786,15 @@ function ResearchCards({ } // ─── Empty state ────────────────────────────────────────────────────────────── -function EmptyState({ onSelect }: { onSelect: (symbol: string) => void }) { +function EmptyState({ + onSelect, + suggestions, +}: { + onSelect: (symbol: string) => void; + suggestions: string[]; +}) { + const cryptoMode = suggestions.some(isCryptoLikeSymbol); + return (
void }) { }}>
📈
- Search a company to get started + Search an asset to get started
- Type a ticker symbol or company name in the search bar above to view charts, financials, and news. + Type a ticker symbol, crypto pair, or company name in the search bar above to view charts, financials, and news.
+ {cryptoMode && ( +
+ Suggested from your crypto bot configuration +
+ )}
- {['AAPL','MSFT','GOOGL','AMZN','NVDA'].map(t => ( - ( + ))}
@@ -790,7 +840,7 @@ function EmptyState({ onSelect }: { onSelect: (symbol: string) => void }) { // ─── HomeView ───────────────────────────────────────────────────────────────── export function HomeView() { - const { activeSymbol, setActiveSymbol } = useAppContext(); + const { activeSymbol, setActiveSymbol, botState, profile: activeProfile } = useAppContext(); const [profile, setProfile] = useState(null); const [profileLoading, setProfileLoading] = useState(false); const [latestBarTimestamp, setLatestBarTimestamp] = useState(null); @@ -819,7 +869,14 @@ export function HomeView() { return () => { cancelled = true; }; }, [activeSymbol]); - if (!activeSymbol) return ; + if (!activeSymbol) { + return ( + + ); + } return (