← back to PoppyPetitions
components/AppShell.tsx
129 lines
'use client';
import { useState } from 'react';
import { LogOut, Menu } from 'lucide-react';
import { AuthProvider, useAuth } from './AuthProvider';
import { ToastProvider } from './ToastProvider';
import Sidebar, { type TabId } from './Sidebar';
import PetitionFeed from './feed/PetitionFeed';
import AgentDirectory from './agents/AgentDirectory';
import CreatePetition from './create/CreatePetition';
import ComputeDashboard from './compute/ComputeDashboard';
import SettingsPanel from './settings/SettingsPanel';
/* ---- Inner shell (needs auth context) ---- */
function Shell() {
const { user, logout } = useAuth();
const [activeTab, setActiveTab] = useState<TabId>('feed');
const [sidebarOpen, setSidebarOpen] = useState(false);
function handleTabChange(tab: TabId) {
setActiveTab(tab);
setSidebarOpen(false);
}
function renderPanel() {
switch (activeTab) {
case 'feed':
return <PetitionFeed />;
case 'agents':
return <AgentDirectory />;
case 'create':
return <CreatePetition onCreated={() => setActiveTab('feed')} />;
case 'compute':
return <ComputeDashboard />;
case 'settings':
return <SettingsPanel />;
default:
return null;
}
}
const TAB_LABELS: Record<TabId, string> = {
feed: 'Petition Feed',
agents: 'Agent Directory',
create: 'Create Petition',
compute: 'Compute Billing',
settings: 'Settings',
};
return (
<div
className="flex"
style={{ backgroundColor: 'var(--color-bg)', minHeight: '100vh' }}
>
<Sidebar
activeTab={activeTab}
onTabChange={handleTabChange}
isOpen={sidebarOpen}
onToggle={() => setSidebarOpen((p) => !p)}
/>
<div className="flex flex-col flex-1 min-w-0">
{/* Top Header Bar */}
<header
className="flex items-center justify-between px-5 h-14 shrink-0"
style={{
backgroundColor: 'var(--color-surface)',
borderBottom: '1px solid var(--color-border)',
}}
>
<div className="flex items-center gap-2">
<button
className="btn btn-ghost btn-sm"
onClick={() => setSidebarOpen((p) => !p)}
style={{ display: 'none' }}
aria-label="Toggle menu"
id="mobile-menu-btn"
>
<Menu size={18} />
</button>
<h1
className="text-sm font-semibold"
style={{ color: 'var(--color-text)' }}
>
{TAB_LABELS[activeTab]}
</h1>
</div>
<div className="flex items-center gap-3">
{user && (
<span
className="text-xs hidden sm:inline"
style={{ color: 'var(--color-text-muted)' }}
>
{user}
</span>
)}
<button
onClick={logout}
className="btn btn-ghost btn-sm"
aria-label="Sign out"
title="Sign out"
>
<LogOut size={15} aria-hidden="true" />
<span className="hidden sm:inline">Sign out</span>
</button>
</div>
</header>
{/* Main Content */}
<main className="flex-1 overflow-auto">
{renderPanel()}
</main>
</div>
</div>
);
}
/* ---- Public export ---- */
export default function AppShell() {
return (
<AuthProvider>
<ToastProvider>
<Shell />
</ToastProvider>
</AuthProvider>
);
}