Spaces:
Sleeping
Sleeping
Dhruva P Gowda
Deploy ShockMap MVP: High-fidelity UI overhaul, animated graph particles, live GDELT ingestion, and production scaffolding
f59a149 Download frontend/src/components/AppShell.jsx from shield137/shockmap-api: direct link, hf CLI and curl.
- Browser
- Download file 4.86 kB
-
https://huggingface.co/spaces/shield137/shockmap-api/resolve/main/frontend/src/components/AppShell.jsx
- Command line
-
hf download hf://spaces/shield137/shockmap-api/frontend/src/components/AppShell.jsx
-
curl -L -o AppShell.jsx https://huggingface.co/spaces/shield137/shockmap-api/resolve/main/frontend/src/components/AppShell.jsx
4.86 kB
| import { useEffect, useState } from 'react'; | |
| import { Outlet, NavLink } from 'react-router-dom'; | |
| import { api } from '../api/client'; | |
| const NAV = [ | |
| { to: '/dashboard', label: 'Dashboard', icon: 'DB' }, | |
| { to: '/map', label: 'Map View', icon: 'MP' }, | |
| { to: '/alerts', label: 'Live Shocks', icon: 'LS' }, | |
| { to: '/query', label: 'Ask ShockMap', icon: 'AI' }, | |
| { to: '/drugs', label: 'Inputs', icon: 'IN' }, | |
| { to: '/graph', label: 'Graph', icon: 'GR' }, | |
| { to: '/simulate', label: 'Simulate', icon: 'SM' }, | |
| ]; | |
| export default function AppShell({ children, selectedSectors }) { | |
| const [health, setHealth] = useState(null); | |
| useEffect(() => { | |
| api.health().then(setHealth).catch(() => setHealth(null)); | |
| }, []); | |
| const isHybridFeed = health?.shock_feed_mode === 'hybrid_demo_live'; | |
| const isDemoOnly = health?.shock_feed_mode === 'demo'; | |
| const feedText = isHybridFeed | |
| ? `Hybrid feed | ${health?.live_shocks || 0} live + ${health?.demo_scenarios || 0} scenarios` | |
| : isDemoOnly | |
| ? `Curated scenarios | ${health?.demo_scenarios || 0} loaded` | |
| : 'GDELT live | 15 min'; | |
| return ( | |
| <div style={{ display: 'flex', minHeight: '100vh', background: 'var(--bg)' }}> | |
| <aside | |
| style={{ | |
| width: 220, | |
| flexShrink: 0, | |
| background: 'var(--surface)', | |
| borderRight: '1px solid var(--border)', | |
| display: 'flex', | |
| flexDirection: 'column', | |
| padding: '24px 0', | |
| position: 'sticky', | |
| top: 0, | |
| height: '100vh', | |
| }} | |
| > | |
| <div style={{ padding: '0 20px 28px', display: 'flex', alignItems: 'center', gap: 10 }}> | |
| <div | |
| style={{ | |
| width: 30, | |
| height: 30, | |
| borderRadius: 8, | |
| background: 'linear-gradient(135deg, #4f9cf9, #8b5cf6)', | |
| display: 'flex', | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| fontSize: 12, | |
| fontWeight: 700, | |
| color: '#fff', | |
| }} | |
| > | |
| SM | |
| </div> | |
| <span style={{ fontWeight: 700, letterSpacing: '-0.02em' }}>ShockMap</span> | |
| </div> | |
| {selectedSectors && selectedSectors.length > 0 && ( | |
| <div style={{ padding: '0 20px 20px' }}> | |
| <p | |
| style={{ | |
| fontSize: '0.65rem', | |
| color: 'var(--muted)', | |
| fontWeight: 600, | |
| textTransform: 'uppercase', | |
| letterSpacing: '0.08em', | |
| marginBottom: 8, | |
| }} | |
| > | |
| Monitoring | |
| </p> | |
| <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}> | |
| {selectedSectors.map((sector) => ( | |
| <span | |
| key={sector} | |
| style={{ | |
| fontSize: '0.7rem', | |
| padding: '3px 9px', | |
| borderRadius: 999, | |
| background: 'rgba(79,156,249,0.1)', | |
| color: 'var(--primary)', | |
| border: '1px solid rgba(79,156,249,0.25)', | |
| }} | |
| > | |
| {sector === 'rare_earth' ? 'Rare Earths' : sector.charAt(0).toUpperCase() + sector.slice(1)} | |
| </span> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| <div style={{ width: '100%', height: 1, background: 'var(--border)', marginBottom: 12 }} /> | |
| <nav style={{ flex: 1, padding: '0 10px' }}> | |
| {NAV.map((item) => ( | |
| <NavLink | |
| key={item.to} | |
| to={item.to} | |
| style={({ isActive }) => ({ | |
| display: 'flex', | |
| alignItems: 'center', | |
| gap: 11, | |
| padding: '9px 12px', | |
| borderRadius: 9, | |
| marginBottom: 3, | |
| textDecoration: 'none', | |
| fontSize: '0.875rem', | |
| fontWeight: 500, | |
| color: isActive ? 'var(--primary)' : 'var(--muted)', | |
| background: isActive ? 'rgba(79,156,249,0.1)' : 'transparent', | |
| transition: 'all 0.15s', | |
| })} | |
| > | |
| <span style={{ fontSize: 11, fontWeight: 700, minWidth: 16 }}>{item.icon}</span> | |
| {item.label} | |
| </NavLink> | |
| ))} | |
| </nav> | |
| <div style={{ padding: '12px 20px', borderTop: '1px solid var(--border)' }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: 7, fontSize: '0.72rem', color: 'var(--muted)' }}> | |
| <span className="live-dot" style={{ width: 6, height: 6 }} /> | |
| {feedText} | |
| </div> | |
| </div> | |
| </aside> | |
| <main style={{ flex: 1, overflow: 'auto', minHeight: '100vh' }}> | |
| {children || <Outlet />} | |
| </main> | |
| </div> | |
| ); | |
| } | |