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
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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user