Overview: regime to the nav bar, watchlist up top
The market regime banner took a full-width row on the dashboard for ambient context. It is now a subtle status in the top bar (colored dot + "neutral regime", full headline and the longs/shorts caution in the tooltip) linking to /regime - matching the mockup's nav. The watchlist strip moves from the bottom of the dashboard (where it got lost) into the freed spot right under the hero: a slim chip row with today's move per symbol and the full-watchlist link. The empty state is a one-line hint instead of a callout box. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { useAuthStore } from '../../stores/authStore';
|
import { useAuthStore } from '../../stores/authStore';
|
||||||
import { check as healthCheck } from '../../api/health';
|
import { check as healthCheck } from '../../api/health';
|
||||||
import { getRunningJobs } from '../../api/jobs';
|
import { getRunningJobs } from '../../api/jobs';
|
||||||
|
import { useMarketRegime } from '../../hooks/useMarketRegime';
|
||||||
|
import { regimeDot, regimeHeadline } from '../../lib/regime';
|
||||||
import TickerSearch from './TickerSearch';
|
import TickerSearch from './TickerSearch';
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
@@ -41,6 +43,7 @@ export default function TopBar() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const running = jobs.data?.running ?? [];
|
const running = jobs.data?.running ?? [];
|
||||||
|
const regime = useMarketRegime();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="glass sticky top-0 z-40 hidden items-center gap-7 rounded-none border-x-0 border-t-0 px-8 py-3 lg:flex">
|
<header className="glass sticky top-0 z-40 hidden items-center gap-7 rounded-none border-x-0 border-t-0 px-8 py-3 lg:flex">
|
||||||
@@ -73,6 +76,25 @@ export default function TopBar() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ml-auto flex items-center gap-5">
|
<div className="ml-auto flex items-center gap-5">
|
||||||
|
{/* Market regime — ambient status; the full picture lives on /regime */}
|
||||||
|
{regime.data && (
|
||||||
|
<NavLink
|
||||||
|
to="/regime"
|
||||||
|
className="group flex items-center gap-2"
|
||||||
|
title={`${regimeHeadline(regime.data)}${
|
||||||
|
regime.data.label === 'bearish'
|
||||||
|
? ' — be cautious on new longs'
|
||||||
|
: regime.data.label === 'bullish'
|
||||||
|
? ' — shorts are counter-trend'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className={`inline-block h-1.5 w-1.5 rounded-full ${regimeDot(regime.data.label)}`} />
|
||||||
|
<span className="text-[11px] capitalize text-gray-500 transition-colors group-hover:text-gray-300">
|
||||||
|
{regime.data.label} regime
|
||||||
|
</span>
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
{/* Live background-job activity, condensed */}
|
{/* Live background-job activity, condensed */}
|
||||||
{running.length > 0 && (
|
{running.length > 0 && (
|
||||||
<span className="flex max-w-[220px] items-center gap-2" title={running.map((j) => j.label).join(' · ')}>
|
<span className="flex max-w-[220px] items-center gap-2" title={running.map((j) => j.label).join(' · ')}>
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import { useTrades } from '../hooks/useTrades';
|
|||||||
import { useWatchlist } from '../hooks/useWatchlist';
|
import { useWatchlist } from '../hooks/useWatchlist';
|
||||||
import { usePaperTrades } from '../hooks/usePaperTrades';
|
import { usePaperTrades } from '../hooks/usePaperTrades';
|
||||||
import { useTickerNames } from '../hooks/useTickers';
|
import { useTickerNames } from '../hooks/useTickers';
|
||||||
import { useMarketRegime } from '../hooks/useMarketRegime';
|
|
||||||
import { regimeColor, regimeDot, regimeHeadline } from '../lib/regime';
|
|
||||||
import { Callout } from '../components/ui/Callout';
|
import { Callout } from '../components/ui/Callout';
|
||||||
import { Section } from '../components/ui/Section';
|
import { Section } from '../components/ui/Section';
|
||||||
import { OpenTradesPanel } from '../components/dashboard/OpenTradesPanel';
|
import { OpenTradesPanel } from '../components/dashboard/OpenTradesPanel';
|
||||||
@@ -213,7 +211,6 @@ export default function DashboardPage() {
|
|||||||
const tickerNames = useTickerNames();
|
const tickerNames = useTickerNames();
|
||||||
const activation = useActivation();
|
const activation = useActivation();
|
||||||
const openTrades = usePaperTrades('open');
|
const openTrades = usePaperTrades('open');
|
||||||
const regime = useMarketRegime();
|
|
||||||
|
|
||||||
const qualifiedSetups = useMemo(
|
const qualifiedSetups = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -342,21 +339,35 @@ export default function DashboardPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Market regime banner */}
|
{/* Watchlist — a slim glanceable strip right up top (regime moved to the nav bar) */}
|
||||||
{regime.data && (
|
{watchlist.data && (
|
||||||
<div className="glass-sm flex items-center gap-2.5 px-4 py-2.5">
|
topWatchlist.length > 0 ? (
|
||||||
<span className={`inline-block h-2 w-2 rounded-full ${regimeDot(regime.data.label)}`} />
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<span className="text-sm text-gray-400">Market regime:</span>
|
<span className="section-index mr-1">Watchlist</span>
|
||||||
<span className={`text-sm font-semibold ${regimeColor(regime.data.label)}`}>
|
{topWatchlist.map((entry) => (
|
||||||
{regimeHeadline(regime.data)}
|
<Link
|
||||||
</span>
|
key={entry.symbol}
|
||||||
{regime.data.label === 'bearish' && (
|
to={`/ticker/${entry.symbol}`}
|
||||||
<span className="text-xs text-gray-500">— be cautious on new longs</span>
|
className="inline-flex items-baseline gap-2 rounded-full border border-white/[0.09] px-3 py-1 text-[12.5px] transition-colors hover:border-blue-400/40 hover:bg-white/[0.03]"
|
||||||
)}
|
>
|
||||||
{regime.data.label === 'bullish' && (
|
<b className="font-semibold text-gray-200">{entry.symbol}</b>
|
||||||
<span className="text-xs text-gray-500">— shorts are counter-trend</span>
|
<span className={`num text-[11px] ${rColor(entry.change_pct)}`}>
|
||||||
)}
|
{entry.change_pct != null
|
||||||
</div>
|
? `${entry.change_pct >= 0 ? '+' : ''}${entry.change_pct.toFixed(2)}%`
|
||||||
|
: '—'}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<Link to="/market" className="ml-1 text-xs font-medium text-blue-300 transition-colors hover:text-blue-200">
|
||||||
|
Full watchlist →
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-gray-600">
|
||||||
|
<span className="section-index mr-2">Watchlist</span>
|
||||||
|
empty — open any ticker and tap ☆ to add it.
|
||||||
|
</p>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Focal setup */}
|
{/* Focal setup */}
|
||||||
@@ -496,37 +507,6 @@ export default function DashboardPage() {
|
|||||||
</Section>
|
</Section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Watchlist — compact chips, drill into any ticker */}
|
|
||||||
<Section title="My Watchlist" hint="today's move">
|
|
||||||
{watchlist.isLoading && <SkeletonTable rows={2} cols={4} />}
|
|
||||||
{watchlist.isError && <Callout variant="error">Failed to load watchlist</Callout>}
|
|
||||||
{watchlist.data && topWatchlist.length === 0 && (
|
|
||||||
<Callout variant="empty">
|
|
||||||
Your watchlist is empty — open any ticker and tap “☆ Add to watchlist”.
|
|
||||||
</Callout>
|
|
||||||
)}
|
|
||||||
{topWatchlist.length > 0 && (
|
|
||||||
<div className="flex flex-wrap items-center gap-2.5">
|
|
||||||
{topWatchlist.map((entry) => (
|
|
||||||
<Link
|
|
||||||
key={entry.symbol}
|
|
||||||
to={`/ticker/${entry.symbol}`}
|
|
||||||
className="inline-flex items-baseline gap-2 rounded-full border border-white/[0.09] px-3.5 py-1.5 text-[13px] transition-colors hover:border-blue-400/40 hover:bg-white/[0.03]"
|
|
||||||
>
|
|
||||||
<b className="font-semibold text-gray-200">{entry.symbol}</b>
|
|
||||||
<span className={`num text-[11.5px] ${rColor(entry.change_pct)}`}>
|
|
||||||
{entry.change_pct != null
|
|
||||||
? `${entry.change_pct >= 0 ? '+' : ''}${entry.change_pct.toFixed(2)}%`
|
|
||||||
: '—'}
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
<Link to="/market" className="ml-1 text-xs font-medium text-blue-300 transition-colors hover:text-blue-200">
|
|
||||||
Full watchlist →
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Section>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user