feat(pitch-deck): passwordless investor auth, audit logs, snapshots & PWA (#2)
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 27s
CI / test-python-voice (push) Successful in 25s
CI / test-bqas (push) Successful in 27s
CI / Deploy (push) Successful in 6s

Adds investor-facing access controls, persistence, and PWA support to the pitch deck:

- Passwordless magic-link auth (jose JWT + nodemailer SMTP)
- Per-investor audit logging (logins, slide views, assumption changes, chat)
- Financial model snapshot persistence (auto-save/restore per investor)
- PWA support (manifest, service worker, offline caching, branded icons)
- Safeguards: email watermark overlay, security headers, content protection,
  rate limiting, IP/new-IP detection, single active session per investor
- Admin API: invite, list investors, revoke, query audit logs
- pitch-deck service added to docker-compose.coolify.yml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #2.
This commit is contained in:
2026-04-07 08:48:38 +00:00
parent 3a2567b44d
commit 645973141c
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>