feat(pitch-deck): add passwordless investor auth, audit logs, snapshots & PWA

Implement a complete investor access system for the pitch deck:

- Passwordless magic link auth (jose JWT + nodemailer SMTP)
- Per-investor audit logging (slide views, assumption changes, chat)
- Financial model snapshot persistence (auto-save/restore per investor)
- PWA support (manifest, service worker, offline caching, icons)
- Security safeguards (watermark overlay, rate limiting, anti-scraping
  headers, content protection, single-session enforcement)
- Admin API for invite/revoke/audit-log management
- Integrated into docker-compose.coolify.yml for production deployment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-05 09:37:50 +02:00
parent 3a2567b44d
commit 244071d63b
35 changed files with 4232 additions and 14 deletions

View File

@@ -6,7 +6,9 @@ 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 { useAuditTracker } from '@/lib/hooks/useAuditTracker'
import { Language, PitchData } from '@/lib/types'
import { Investor } from '@/lib/hooks/useAuth'
import ParticleBackground from './ParticleBackground'
import ProgressBar from './ProgressBar'
@@ -17,6 +19,7 @@ import SlideOverview from './SlideOverview'
import SlideContainer from './SlideContainer'
import PresenterOverlay from './presenter/PresenterOverlay'
import AvatarPlaceholder from './presenter/AvatarPlaceholder'
import Watermark from './Watermark'
import IntroPresenterSlide from './slides/IntroPresenterSlide'
import CoverSlide from './slides/CoverSlide'
@@ -42,9 +45,11 @@ import AIPipelineSlide from './slides/AIPipelineSlide'
interface PitchDeckProps {
lang: Language
onToggleLanguage: () => void
investor: Investor | null
onLogout: () => void
}
export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
export default function PitchDeck({ lang, onToggleLanguage, investor, onLogout }: PitchDeckProps) {
const { data, loading, error } = usePitchData()
const nav = useSlideNavigation()
const [fabOpen, setFabOpen] = useState(false)
@@ -56,6 +61,13 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
language: lang,
})
// Audit tracking
useAuditTracker({
investorId: investor?.id || null,
currentSlide: nav.currentSlide,
enabled: !!investor,
})
const toggleFullscreen = useCallback(() => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen()
@@ -137,7 +149,7 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
case 'team':
return <TeamSlide lang={lang} team={data.team} />
case 'financials':
return <FinancialsSlide lang={lang} />
return <FinancialsSlide lang={lang} investorId={investor?.id || null} />
case 'the-ask':
return <TheAskSlide lang={lang} funding={data.funding} />
case 'ai-qa':
@@ -160,10 +172,16 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
}
return (
<div className="h-screen relative overflow-hidden bg-gradient-to-br from-slate-950 via-[#0a0a1a] to-slate-950">
<div
className="h-screen relative overflow-hidden bg-gradient-to-br from-slate-950 via-[#0a0a1a] to-slate-950 select-none"
onContextMenu={(e) => e.preventDefault()}
>
<ParticleBackground />
<ProgressBar current={nav.currentIndex} total={nav.totalSlides} />
{/* Investor watermark */}
{investor && <Watermark text={investor.email} />}
<SlideContainer slideKey={nav.currentSlide} direction={nav.direction}>
{renderSlide()}
</SlideContainer>