71 lines
2.5 KiB
TypeScript
71 lines
2.5 KiB
TypeScript
import { useEffect, useMemo, useState, type ReactNode } from 'react';
|
|
import { Flame, Gem, Heart, Wifi, WifiOff } from 'lucide-react';
|
|
import { useCompanion } from '../../api/CompanionContext.js';
|
|
import type { ScreenId } from '../../app/navigation.js';
|
|
import { BottomNav } from '../bottom-nav/BottomNav.js';
|
|
|
|
type AppShellProps = {
|
|
activeScreen: ScreenId;
|
|
children: ReactNode;
|
|
onNavigate: (screen: ScreenId) => void;
|
|
};
|
|
|
|
const screenTitles: Record<ScreenId, { title: string; kicker: string }> = {
|
|
chat: { title: 'Quest', kicker: 'Hermes training path' },
|
|
files: { title: 'Backpack', kicker: 'Workspace loot' },
|
|
terminal: { title: 'Power move', kicker: 'Run a command' },
|
|
activity: { title: 'League', kicker: 'Companion stats' },
|
|
settings: { title: 'Nest', kicker: 'Connection setup' },
|
|
};
|
|
|
|
export function AppShell({ activeScreen, children, onNavigate }: AppShellProps) {
|
|
const { client } = useCompanion();
|
|
const [online, setOnline] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
client
|
|
.health()
|
|
.then(() => {
|
|
if (!cancelled) setOnline(true);
|
|
})
|
|
.catch(() => {
|
|
if (!cancelled) setOnline(false);
|
|
});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [client]);
|
|
|
|
const title = useMemo(() => screenTitles[activeScreen], [activeScreen]);
|
|
|
|
return (
|
|
<div className="phone-app">
|
|
<div className="app-shell">
|
|
<header className="app-header">
|
|
<div className="avatar-badge" aria-hidden="true">⚡</div>
|
|
<div className="app-header__copy">
|
|
<p className="eyebrow">{title.kicker}</p>
|
|
<h1>{title.title}</h1>
|
|
</div>
|
|
<div className="top-stats" aria-label="Hermes status summary">
|
|
<span className="stat-chip stat-chip--fire"><Flame size={16} fill="currentColor" />7</span>
|
|
<span className="stat-chip stat-chip--gem"><Gem size={16} fill="currentColor" />550</span>
|
|
<span className="stat-chip stat-chip--heart"><Heart size={16} fill="currentColor" />3</span>
|
|
</div>
|
|
</header>
|
|
|
|
<div className={`connection-banner ${online ? 'connection-banner--online' : ''}`}>
|
|
{online ? <Wifi size={18} /> : <WifiOff size={18} />}
|
|
<span>{online ? 'Companion online — ready for quests' : 'Companion offline — check Settings'}</span>
|
|
</div>
|
|
|
|
<main className="app-main">{children}</main>
|
|
<BottomNav activeScreen={activeScreen} onNavigate={onNavigate} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|