Files
breakpilot-core/pitch-deck/components/PitchDeck.tsx
Benjamin Admin a58cd16f01 feat: Finanzplan Phase 1-4 — DB + Engine + API + Spreadsheet-UI
Phase 1: DB-Schema (12 fp_* Tabellen) + Excel-Import (332 Zeilen importiert)
Phase 2: Compute Engine (Personal, Invest, Umsatz, Material, Betrieblich, Liquiditaet, GuV)
Phase 3: API (/api/finanzplan/ — GET sheets, PUT cells, POST compute)
Phase 4: Spreadsheet-UI (FinanzplanSlide als Annex mit Tab-Leiste, editierbarem Grid, Jahres-Navigation)

Zusaetzlich:
- Gruendungsdatum verschoben: Feb→Aug 2026 (DB + Personalkosten)
- Neue Preisstaffel: Startup/<10 MA ab 3.600 EUR/Jahr (14-Tage-Test, Kreditkarte)
- Competition-Slide: Pricing-Tiers aktualisiert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 19:26:46 +01:00

243 lines
8.2 KiB
TypeScript

'use client'
import { useCallback, useState } from 'react'
import { AnimatePresence } from 'framer-motion'
import { useSlideNavigation } from '@/lib/hooks/useSlideNavigation'
import { useKeyboard } from '@/lib/hooks/useKeyboard'
import { usePitchData } from '@/lib/hooks/usePitchData'
import { usePresenterMode } from '@/lib/hooks/usePresenterMode'
import { Language, PitchData } from '@/lib/types'
import ParticleBackground from './ParticleBackground'
import ProgressBar from './ProgressBar'
import NavigationControls from './NavigationControls'
import NavigationFAB from './NavigationFAB'
import ChatFAB from './ChatFAB'
import SlideOverview from './SlideOverview'
import SlideContainer from './SlideContainer'
import PresenterOverlay from './presenter/PresenterOverlay'
import AvatarPlaceholder from './presenter/AvatarPlaceholder'
import IntroPresenterSlide from './slides/IntroPresenterSlide'
import ExecutiveSummarySlide from './slides/ExecutiveSummarySlide'
import CoverSlide from './slides/CoverSlide'
import ProblemSlide from './slides/ProblemSlide'
import SolutionSlide from './slides/SolutionSlide'
import RegulatoryLandscapeSlide from './slides/RegulatoryLandscapeSlide'
import ProductSlide from './slides/ProductSlide'
import HowItWorksSlide from './slides/HowItWorksSlide'
import MarketSlide from './slides/MarketSlide'
import BusinessModelSlide from './slides/BusinessModelSlide'
import TractionSlide from './slides/TractionSlide'
import CompetitionSlide from './slides/CompetitionSlide'
import TeamSlide from './slides/TeamSlide'
import FinancialsSlide from './slides/FinancialsSlide'
import TheAskSlide from './slides/TheAskSlide'
import AIQASlide from './slides/AIQASlide'
import AssumptionsSlide from './slides/AssumptionsSlide'
import ArchitectureSlide from './slides/ArchitectureSlide'
import GTMSlide from './slides/GTMSlide'
import RegulatorySlide from './slides/RegulatorySlide'
import EngineeringSlide from './slides/EngineeringSlide'
import AIPipelineSlide from './slides/AIPipelineSlide'
import SDKDemoSlide from './slides/SDKDemoSlide'
import FinanzplanSlide from './slides/FinanzplanSlide'
interface PitchDeckProps {
lang: Language
onToggleLanguage: () => void
}
export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
const { data, loading, error } = usePitchData()
const nav = useSlideNavigation()
const [fabOpen, setFabOpen] = useState(false)
const presenter = usePresenterMode({
goToSlide: nav.goToSlide,
currentSlide: nav.currentIndex,
totalSlides: nav.totalSlides,
language: lang,
})
const toggleFullscreen = useCallback(() => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen()
} else {
document.exitFullscreen()
}
}, [])
const toggleMenu = useCallback(() => {
setFabOpen(prev => !prev)
}, [])
useKeyboard({
onNext: nav.nextSlide,
onPrev: nav.prevSlide,
onFirst: nav.goToFirst,
onLast: nav.goToLast,
onOverview: nav.toggleOverview,
onFullscreen: toggleFullscreen,
onLanguageToggle: onToggleLanguage,
onMenuToggle: toggleMenu,
onPresenterToggle: presenter.toggle,
onGoToSlide: nav.goToSlide,
enabled: !nav.showOverview,
})
if (loading) {
return (
<div className="h-screen flex items-center justify-center">
<div className="text-center">
<div className="w-12 h-12 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<p className="text-white/40 text-sm">{lang === 'de' ? 'Lade Pitch-Daten...' : 'Loading pitch data...'}</p>
</div>
</div>
)
}
if (error || !data) {
return (
<div className="h-screen flex items-center justify-center">
<div className="text-center max-w-md">
<p className="text-red-400 mb-2">{lang === 'de' ? 'Fehler beim Laden' : 'Loading error'}</p>
<p className="text-white/40 text-sm">{error || 'No data'}</p>
</div>
</div>
)
}
function renderSlide() {
if (!data) return null
switch (nav.currentSlide) {
case 'intro-presenter':
return (
<IntroPresenterSlide
lang={lang}
onStartPresenter={presenter.start}
isPresenting={presenter.state !== 'idle'}
/>
)
case 'executive-summary':
return <ExecutiveSummarySlide lang={lang} data={data} />
case 'cover':
return <CoverSlide lang={lang} onNext={nav.nextSlide} funding={data.funding} />
case 'problem':
return <ProblemSlide lang={lang} />
case 'solution':
return <SolutionSlide lang={lang} />
case 'regulatory-landscape':
return <RegulatoryLandscapeSlide lang={lang} />
case 'product':
return <ProductSlide lang={lang} />
case 'how-it-works':
return <HowItWorksSlide lang={lang} />
case 'market':
return <MarketSlide lang={lang} market={data.market} />
case 'business-model':
return <BusinessModelSlide lang={lang} />
case 'traction':
return <TractionSlide lang={lang} milestones={data.milestones} metrics={data.metrics} />
case 'competition':
return <CompetitionSlide lang={lang} features={data.features} competitors={data.competitors} />
case 'team':
return <TeamSlide lang={lang} team={data.team} />
case 'financials':
return <FinancialsSlide lang={lang} />
case 'the-ask':
return <TheAskSlide lang={lang} funding={data.funding} />
case 'ai-qa':
return <AIQASlide lang={lang} />
case 'annex-assumptions':
return <AssumptionsSlide lang={lang} />
case 'annex-architecture':
return <ArchitectureSlide lang={lang} />
case 'annex-gtm':
return <GTMSlide lang={lang} />
case 'annex-regulatory':
return <RegulatorySlide lang={lang} />
case 'annex-engineering':
return <EngineeringSlide lang={lang} />
case 'annex-aipipeline':
return <AIPipelineSlide lang={lang} />
case 'annex-sdk-demo':
return <SDKDemoSlide lang={lang} />
case 'annex-finanzplan':
return <FinanzplanSlide lang={lang} />
default:
return null
}
}
return (
<div className="h-screen relative overflow-hidden bg-gradient-to-br from-slate-950 via-[#0a0a1a] to-slate-950">
<ParticleBackground />
<ProgressBar current={nav.currentIndex} total={nav.totalSlides} />
<SlideContainer slideKey={nav.currentSlide} direction={nav.direction}>
{renderSlide()}
</SlideContainer>
<NavigationControls
onPrev={nav.prevSlide}
onNext={nav.nextSlide}
isFirst={nav.isFirst}
isLast={nav.isLast}
current={nav.currentIndex}
total={nav.totalSlides}
/>
<ChatFAB
lang={lang}
currentSlide={nav.currentSlide}
currentIndex={nav.currentIndex}
visitedSlides={nav.visitedSlides}
onGoToSlide={nav.goToSlide}
presenterState={presenter.state}
onPresenterInterrupt={presenter.pause}
/>
<NavigationFAB
currentIndex={nav.currentIndex}
totalSlides={nav.totalSlides}
visitedSlides={nav.visitedSlides}
onGoToSlide={nav.goToSlide}
lang={lang}
onToggleLanguage={onToggleLanguage}
/>
{/* Presenter UI */}
<AvatarPlaceholder state={presenter.state} />
<PresenterOverlay
state={presenter.state}
currentIndex={nav.currentIndex}
totalSlides={nav.totalSlides}
progress={presenter.progress}
displayText={presenter.displayText}
lang={lang}
onPause={presenter.pause}
onResume={presenter.resume}
onStop={presenter.stop}
onSkip={presenter.skipSlide}
isSpeaking={presenter.isSpeaking}
ttsAvailable={presenter.ttsAvailable}
ttsEnabled={presenter.ttsEnabled}
onToggleTts={() => presenter.setTtsEnabled(!presenter.ttsEnabled)}
/>
<AnimatePresence>
{nav.showOverview && (
<SlideOverview
currentIndex={nav.currentIndex}
onGoToSlide={nav.goToSlide}
onClose={() => nav.setShowOverview(false)}
lang={lang}
/>
)}
</AnimatePresence>
</div>
)
}