feat(pitch-deck): add passwordless investor auth, audit logs, snapshots & PWA
Some checks failed
CI / go-lint (pull_request) Failing after 17s
CI / python-lint (pull_request) Failing after 12s
CI / nodejs-lint (pull_request) Failing after 7s
CI / test-go-consent (pull_request) Failing after 11s
CI / test-python-voice (pull_request) Failing after 11s
CI / test-bqas (pull_request) Failing after 11s
CI / Deploy (pull_request) Has been skipped
Some checks failed
CI / go-lint (pull_request) Failing after 17s
CI / python-lint (pull_request) Failing after 12s
CI / nodejs-lint (pull_request) Failing after 7s
CI / test-go-consent (pull_request) Failing after 11s
CI / test-python-voice (pull_request) Failing after 11s
CI / test-bqas (pull_request) Failing after 11s
CI / Deploy (pull_request) Has been skipped
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:
73
pitch-deck/lib/hooks/useAuditTracker.ts
Normal file
73
pitch-deck/lib/hooks/useAuditTracker.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useCallback } from 'react'
|
||||
|
||||
interface AuditTrackerOptions {
|
||||
investorId: string | null
|
||||
currentSlide: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export function useAuditTracker({ investorId, currentSlide, enabled }: AuditTrackerOptions) {
|
||||
const lastSlide = useRef<string>('')
|
||||
const slideTimestamps = useRef<Map<string, number>>(new Map())
|
||||
const pendingEvents = useRef<Array<{ action: string; details: Record<string, unknown>; slide_id?: string }>>([])
|
||||
const flushTimer = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
const flush = useCallback(async () => {
|
||||
if (pendingEvents.current.length === 0) return
|
||||
const events = [...pendingEvents.current]
|
||||
pendingEvents.current = []
|
||||
|
||||
// Send events one at a time (they're debounced so there shouldn't be many)
|
||||
for (const event of events) {
|
||||
try {
|
||||
await fetch('/api/audit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(event),
|
||||
})
|
||||
} catch {
|
||||
// Silently fail - audit should not block UX
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const track = useCallback((action: string, details: Record<string, unknown> = {}, slideId?: string) => {
|
||||
if (!enabled || !investorId) return
|
||||
|
||||
pendingEvents.current.push({ action, details, slide_id: slideId })
|
||||
|
||||
// Debounce flush by 500ms
|
||||
if (flushTimer.current) clearTimeout(flushTimer.current)
|
||||
flushTimer.current = setTimeout(flush, 500)
|
||||
}, [enabled, investorId, flush])
|
||||
|
||||
// Track slide views
|
||||
useEffect(() => {
|
||||
if (!enabled || !investorId || !currentSlide) return
|
||||
if (currentSlide === lastSlide.current) return
|
||||
|
||||
const now = Date.now()
|
||||
const prevTimestamp = slideTimestamps.current.get(lastSlide.current)
|
||||
const dwellTime = prevTimestamp ? now - prevTimestamp : 0
|
||||
|
||||
lastSlide.current = currentSlide
|
||||
slideTimestamps.current.set(currentSlide, now)
|
||||
|
||||
track('slide_viewed', {
|
||||
slide_id: currentSlide,
|
||||
previous_dwell_ms: dwellTime,
|
||||
}, currentSlide)
|
||||
}, [currentSlide, enabled, investorId, track])
|
||||
|
||||
// Flush on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (flushTimer.current) clearTimeout(flushTimer.current)
|
||||
flush()
|
||||
}
|
||||
}, [flush])
|
||||
|
||||
return { track }
|
||||
}
|
||||
Reference in New Issue
Block a user