/* FinPulse app shell. Exports to window. */
const NAV_ITEMS = [
{ key: 'markets', label: 'Markets', icon: 'markets' },
{ key: 'stocks', label: 'Stocks', icon: 'stocks' },
{ key: 'screener', label: 'Screener', icon: 'screener' },
{ key: 'indices', label: 'Indices', icon: 'indices' },
{ key: 'funds', label: 'Mutual Funds', icon: 'funds' },
{ key: 'ipo', label: 'IPO', icon: 'ipo' },
{ key: 'news', label: 'News', icon: 'news' },
{ key: 'calc', label: 'Calculators', icon: 'calc' },
];
function TickerBar({ stocks, onOpenStock }) {
const Item = ({ s }) => (
);
return (
{stocks.map((s) => )}
{stocks.map((s) => )}
);
}
function Brand({ mobile, onHome }) {
const B = window.CSP_BRAND || { name: 'Company Stock Price', shortName: 'CSP', domain: 'companystockprice.com' };
const inner = (
<>
{B.shortName}
Company Stock Price
{!mobile && {B.domain}}
>
);
if (mobile) {
return (
);
}
return (
);
}
function Sidebar({ current, onNav, drawerOpen, onCloseDrawer, onHome }) {
return (
);
}
function userInitials(name) {
const parts = String(name || '').trim().split(/\s+/).filter(Boolean);
if (!parts.length) return null;
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}
function Topbar({ theme, onToggleTheme, onHamburger, onOpenSearch, onHome }) {
const gate = typeof useUsageGate === 'function' ? useUsageGate() : null;
const showProfile = gate && gate.signedIn;
return (
);
}
const BOTTOM_ITEMS = [
{ key: 'markets', label: 'Markets', icon: 'markets' },
{ key: 'stocks', label: 'Stocks', icon: 'stocks' },
{ key: 'news', label: 'News', icon: 'news' },
{ key: 'more', label: 'More', icon: 'more' },
];
function BottomNav({ current, onNav }) {
return (
);
}
function SearchOverlay({ open, onClose, onOpenStock }) {
const L = useLive();
const MD = useMarketData();
const [q, setQ] = useState('');
const [results, setResults] = useState([]);
const [busy, setBusy] = useState(false);
const [loadingSym, setLoadingSym] = useState(null);
const [err, setErr] = useState('');
useEffect(() => { if (open) { setQ(''); setResults([]); setErr(''); } }, [open]);
useEffect(() => {
if (!q.trim()) { setResults([]); return; }
setBusy(true); setErr('');
const id = setTimeout(async () => {
try {
const local = L.market.searchLocal(q.trim(), MD.stocks, 8);
if (L.hasKey) {
const remote = await L.market.searchSymbols(q.trim(), 6, MD.stocks);
const seen = new Set(local.map((r) => r.symbol));
setResults([...local, ...remote.filter((r) => !seen.has(r.symbol))]);
L.refreshRateLimit();
} else {
setResults(local);
}
} catch (e) {
setResults(L.market.searchLocal(q.trim(), MD.stocks, 12));
} finally { setBusy(false); }
}, 300);
return () => clearTimeout(id);
}, [q, L.hasKey, MD.stocks]);
async function openLive(symbol) {
setLoadingSym(symbol); setErr('');
try {
const sym = String(symbol || '');
const local = MD.stocks.find((s) => s.symbol === sym || s.ticker === sym.replace(/\.NS$/i, ''));
if (local) {
onOpenStock(local);
onClose();
return;
}
onOpenStock(await L.market.getStock(symbol));
L.refreshRateLimit();
onClose();
} catch (e) {
setErr(e.code === 'RATE_LIMIT' ? 'Rate limit — wait a moment' : apiErrorMessage(e));
} finally { setLoadingSym(null); }
}
if (!open) return null;
const recent = [...MD.stocks].sort((a, b) => Math.abs(b.chg) - Math.abs(a.chg)).slice(0, 10).map((s) => s.ticker);
return (
setQ(e.target.value)} placeholder="Search NSE stocks (Reliance, TCS…) or US symbols" />
{err &&
{err}
}
{q ? (
busy ? (
{[0, 1, 2, 3].map((i) => (
))}
)
: results.length ? results.map((r) => (
openLive(r.symbol)}>
{r.displaySymbol || r.symbol}
{r.description}
{loadingSym === r.symbol ? loading… : }
)) :
No matches for "{q}"
) : (
<>
Popular symbols
{recent.map((r, i) => (
openLive(r)} style={{ cursor: 'pointer' }}>
{r}
{loadingSym === r ? loading… : }
))}
>
)}
);
}
function Footer({ onNav }) {
const B = window.CSP_BRAND || { legalName: 'Company Stock Price', copyrightYear: 2026, domain: 'companystockprice.com' };
const links = [
{ key: 'about', label: 'About' },
{ key: 'terms', label: 'Terms' },
{ key: 'privacy', label: 'Privacy' },
{ key: 'disclaimer', label: 'Disclaimer' },
];
return (
);
}
Object.assign(window, { NAV_ITEMS, TickerBar, Sidebar, Topbar, BottomNav, SearchOverlay, Footer });