/* Company Stock Price — main app: routing, theme, tweaks. */ const LEGAL_ROUTES = window.CSP_ROUTER.LEGAL; const FONT_MAP = { 'Plus Jakarta Sans': "'Plus Jakarta Sans', system-ui, sans-serif", 'Manrope': "'Manrope', system-ui, sans-serif", 'Space Grotesk': "'Space Grotesk', system-ui, sans-serif", 'Syne': "'Syne', system-ui, sans-serif", }; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "#2563eb", "font": "Plus Jakarta Sans", "radius": 12, "density": "regular" }/*EDITMODE-END*/; function applyRouteState(parsed, setters) { const { setRoute, setScreenerMode, setStock, setStockLoading, setStockError, setArticleId } = setters; const route = parsed.route === 'screener' ? 'screener' : parsed.route; setRoute(route); setScreenerMode(parsed.screenerMode || 'browse'); if (parsed.route === 'newsarticle') setArticleId(parsed.articleId || null); else if (setArticleId) setArticleId(null); if (parsed.route !== 'stockdetail') { setStock(null); setStockLoading(false); setStockError(null); } } function App() { const t0 = useTweaks(TWEAK_DEFAULTS); const t = t0[0], setTweak = t0[1]; const L = useLive(); const MD = useMarketData(); const initial = window.CSP_ROUTER.migrateHashToPath(); const [route, setRoute] = useState(initial.route === 'screener' ? 'screener' : initial.route); const [screenerMode, setScreenerMode] = useState(initial.screenerMode || 'browse'); const [stock, setStock] = useState(null); const [stockSymbol, setStockSymbol] = useState(initial.route === 'stockdetail' ? initial.symbol : null); const [stockLoading, setStockLoading] = useState(initial.route === 'stockdetail'); const [stockError, setStockError] = useState(null); const [articleId, setArticleId] = useState(initial.route === 'newsarticle' ? initial.articleId : null); const [indicesRegion, setIndicesRegion] = useState('IN'); const [indicesSel, setIndicesSel] = useState(null); const [marketsRegion, setMarketsRegion] = useState('IN'); const [drawer, setDrawer] = useState(false); const [search, setSearch] = useState(false); const [theme, setTheme] = useState(() => localStorage.getItem('fp-theme') || 'dark'); const routeSetters = { setRoute, setScreenerMode, setStock, setStockLoading, setStockError, setArticleId, }; useEffect(() => { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('fp-theme', theme); }, [theme]); useEffect(() => { document.documentElement.style.setProperty('--chart-grid', theme === 'dark' ? '#1e293b' : '#f1f5f9'); }, [theme]); useEffect(() => { const h = (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); setSearch(true); } if (e.key === 'Escape') { setSearch(false); setDrawer(false); } }; window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, []); useEffect(() => { const onPop = () => { const parsed = window.CSP_ROUTER.parseLocation(window.location); applyRouteState(parsed, routeSetters); if (parsed.route === 'stockdetail') setStockSymbol(parsed.symbol); else setStockSymbol(null); if (parsed.route === 'newsarticle') setArticleId(parsed.articleId || null); else setArticleId(null); window.scrollTo(0, 0); }; window.addEventListener('popstate', onPop); return () => window.removeEventListener('popstate', onPop); }, []); useEffect(() => { const p = window.location.pathname; if (p === '/' || p === '/index.html') { window.CSP_ROUTER.navigate('markets', {}, true); } }, []); useEffect(() => { if (route !== 'stockdetail' || !stockSymbol) return; if (MD.loading && !MD.ready) return; let cancelled = false; async function loadStockFromUrl() { setStockLoading(true); setStockError(null); const sym = stockSymbol; const ticker = sym.replace(/\.NS$/i, ''); const apiSym = /\.NS$|^\^|INR=X/i.test(sym) ? sym : sym + '.NS'; const local = MD.stocks.find((s) => s.ticker === ticker || s.ticker === sym || s.symbol === sym || s.symbol === apiSym ); if (local) { if (!cancelled) { setStock(local); setStockLoading(false); } return; } try { const s = await L.market.getStock(apiSym); if (!cancelled) { setStock(s); setStockLoading(false); } } catch (e) { if (!cancelled) { setStock(null); setStockLoading(false); setStockError(apiErrorMessage(e)); } } } loadStockFromUrl(); return () => { cancelled = true; }; }, [route, stockSymbol, MD.ready, MD.loading]); function openArticle(item) { if (!item || item.id == null) return; if (window.NewsCache) window.NewsCache.put(item); setArticleId(String(item.id)); setRoute('newsarticle'); window.CSP_ROUTER.navigate('newsarticle', { articleId: item.id }); window.scrollTo(0, 0); } function openStock(s) { const sym = s.ticker || s.symbol || ''; setStock(s); setStockSymbol(sym); setStockLoading(false); setStockError(null); setRoute('stockdetail'); window.CSP_ROUTER.navigate('stockdetail', { symbol: sym }); window.scrollTo(0, 0); } function nav(key, replace) { if (key === 'more') { setDrawer(true); return; } setDrawer(false); const routeKey = key === 'screener' ? 'screener' : key; const modeNext = routeKey === 'screener' ? 'build' : (routeKey === 'stocks' ? 'browse' : screenerMode); setRoute(routeKey); if (routeKey === 'screener' || routeKey === 'stocks') setScreenerMode(modeNext); setStock(null); setStockSymbol(null); setStockLoading(false); setStockError(null); setArticleId(null); if (routeKey !== 'indices') { setIndicesSel(null); } window.CSP_ROUTER.navigate(routeKey, {}, replace); window.scrollTo(0, 0); } function openIndices(region, label) { setIndicesRegion(region || 'IN'); setIndicesSel(label || null); setRoute('indices'); window.CSP_ROUTER.navigate('indices'); window.scrollTo(0, 0); } const appStyle = { '--accent': t.accent, '--accent-soft': `color-mix(in srgb, ${t.accent} 12%, var(--bg-primary))`, '--accent-text': t.accent, '--tw-radius': t.radius + 'px', '--font-body': FONT_MAP[t.font] || FONT_MAP['Plus Jakarta Sans'], }; const sidebarActive = route === 'stockdetail' ? 'stocks' : (route === 'newsarticle' ? 'news' : route); const bottomActive = route === 'stockdetail' ? 'stocks' : (route === 'newsarticle' ? 'news' : ((route === 'screener') ? 'stocks' : route)); const tickerStocks = MD.stocks.length ? MD.stocks : []; if (route === 'admin') { return (
); } let screen; if (route === 'markets') screen = ( ); else if (route === 'screener' || route === 'stocks') screen = ( nav(m === 'build' ? 'screener' : 'stocks')} /> ); else if (route === 'news') screen = ; else if (route === 'newsarticle') screen = ( nav('news')} onOpenArticle={openArticle} /> ); else if (route === 'indices') screen = ( ); else if (route === 'funds') screen = ; else if (route === 'ipo') screen = ; else if (route === 'calc') screen = ; else if (LEGAL_ROUTES.includes(route)) screen = ( { if (page && LEGAL_ROUTES.includes(page)) nav(page); else nav('markets'); }} /> ); else if (route === 'stockdetail') { if (stockLoading) screen = ; else if (stockError) screen = ( nav('stocks')}>Back to stocks} /> ); else if (stock) screen = ( nav('stocks')} onOpenArticle={openArticle} /> ); else screen = ; } else { screen = (

Coming soon

); } return (
{MD.loading && !MD.ready ? : tickerStocks.length > 0 && }
setDrawer(false)} onHome={() => nav('markets')} />
setDrawer(false)}>
setTheme((x) => (x === 'dark' ? 'light' : 'dark'))} onHamburger={() => setDrawer(true)} onOpenSearch={() => setSearch(true)} onHome={() => nav('markets')} />
{screen}
setSearch(false)} onOpenStock={openStock} /> setTweak('font', v)} /> setTweak('accent', v)} /> setTweak('radius', v)} /> setTweak('density', v)} />
); } ReactDOM.createRoot(document.getElementById('root')).render( );