Files
AOS/src/components/aos/app-shell.tsx
T
Z User 39b3636333 feat: reseller self-service portal (clients + licenses + tickets)
- ResellerPortalView with 3 tabs:
  1. Kunden: list agency's clients + create new client (POST /api/aos/clients)
  2. Lizenzen: list agency-filtered licenses + create new license with
     live pricing preview (base + agency discount + yearly discount = net)
  3. Tickets: agency-scoped ticket inbox + escalate button
- Top: agency selector (pick which approved reseller to act as)
- Full integration: store, sidebar, top-bar, command-menu, i18n, app-shell
- Mobile responsive, German labels, no indigo/blue
2026-10-28 23:46:54 +00:00

127 lines
4.8 KiB
TypeScript
Executable File

'use client'
import { useState, useEffect } from 'react'
import { useAos } from '@/store/aos'
import { SidebarNav } from './sidebar-nav'
import { TopBar } from './top-bar'
import { Footer } from './footer'
import { CommandMenu } from './command-menu'
import { CompanyDrawer } from './company-drawer'
import { Sheet, SheetContent, SheetTitle } from '@/components/ui/sheet'
import { DashboardView } from './views/dashboard'
import { CalendarView } from './views/calendar'
import { ReportingView } from './views/reporting'
import { CrmView } from './views/crm'
import { LeadsView } from './views/leads'
import { EstimatesView } from './views/estimates'
import { ProjectsView } from './views/projects'
import { TimesheetView } from './views/timesheet'
import { SupportView } from './views/support'
import { LicensesView } from './views/licenses'
import { AgencyView } from './views/agency'
import { ResellerPortalView } from './views/reseller-portal'
import { InstancesView } from './views/instances'
import { ContractsView } from './views/contracts'
import { ExpensesView } from './views/expenses'
import { FinanceView } from './views/finance'
import { RecurringView } from './views/recurring'
import { AccountingView } from './views/accounting'
import { FinanzamtView } from './views/finanzamt'
import { AuditView } from './views/audit'
import { DataView } from './views/data'
import { KbView } from './views/kb'
import { ApiView } from './views/api'
import { SettingsView } from './views/settings'
import { GlobalSettingsView } from './views/global-settings'
import { SetupWizardView } from './views/setup-wizard'
import { PortalShell } from './portal-shell'
import { OnboardingOverlay } from './onboarding-overlay'
import { MobileBottomNav } from './mobile-bottom-nav'
export function AppShell() {
const { view, sidebarOpen, setSidebarOpen, portalMode } = useAos()
const [mounted, setMounted] = useState(false)
// Hydration gate: wait for client mount before rendering interactive UI
// This prevents Radix UI useId() mismatches between server and client
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setMounted(true)
}, [])
// Portal mode renders a completely separate experience
if (portalMode) return <PortalShell />
// Before mount: render a minimal skeleton to prevent hydration mismatch
if (!mounted) {
return (
<div className="flex min-h-screen w-full bg-background">
<aside className="hidden w-64 shrink-0 border-r border-border md:block" />
<div className="flex min-w-0 flex-1 flex-col">
<header className="h-16 border-b border-border" />
<main className="flex-1" />
<Footer />
</div>
</div>
)
}
return (
<div className="flex min-h-screen w-full bg-background">
{/* Desktop sidebar */}
<aside className="hidden w-64 shrink-0 border-r border-border md:block">
<div className="sticky top-0 h-screen">
<SidebarNav />
</div>
</aside>
{/* Mobile sidebar */}
<Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>
<SheetContent side="left" className="w-72 p-0">
<SheetTitle className="sr-only">Navigation</SheetTitle>
<SidebarNav />
</SheetContent>
</Sheet>
{/* Main */}
<div className="flex min-w-0 flex-1 flex-col">
<TopBar />
<main className="flex-1 pb-16 md:pb-0">
{view === 'dashboard' && <DashboardView />}
{view === 'calendar' && <CalendarView />}
{view === 'reporting' && <ReportingView />}
{view === 'crm' && <CrmView />}
{view === 'leads' && <LeadsView />}
{view === 'estimates' && <EstimatesView />}
{view === 'projects' && <ProjectsView />}
{view === 'timesheet' && <TimesheetView />}
{view === 'support' && <SupportView />}
{view === 'licenses' && <LicensesView />}
{view === 'agency' && <AgencyView />}
{view === 'reseller_portal' && <ResellerPortalView />}
{view === 'instances' && <InstancesView />}
{view === 'contracts' && <ContractsView />}
{view === 'expenses' && <ExpensesView />}
{view === 'finance' && <FinanceView />}
{view === 'recurring' && <RecurringView />}
{view === 'accounting' && <AccountingView />}
{view === 'finanzamt' && <FinanzamtView />}
{view === 'audit' && <AuditView />}
{view === 'data' && <DataView />}
{view === 'kb' && <KbView />}
{view === 'api' && <ApiView />}
{view === 'settings' && <SettingsView />}
{view === 'settings_global' && <GlobalSettingsView />}
{view === 'setup' && <SetupWizardView />}
</main>
<Footer />
</div>
<CommandMenu />
<CompanyDrawer />
<OnboardingOverlay />
<MobileBottomNav />
</div>
)
}