Files
breakpilot-core/pitch-deck/app/auth/verify/page.tsx
T
Sharang Parnerkar 5946aa47d5
Build pitch-deck / build-push-deploy (push) Successful in 1m37s
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 38s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 30s
fix(pitch-deck): GDPR compliance — automated cleanup, full Art. 13 notice
- runDataCleanup() replaces maskOverdueInvestors(): now also anonymizes
  never-activated invites after 90 days, deletes sessions + magic links
  older than 30 days, NULLs IPs in audit logs older than 30 days, and
  redacts email from audit log details JSONB for masked investors
- New /api/admin/cleanup POST endpoint for scheduled invocation
- New .gitea/workflows/pitch-cleanup.yml: daily cron at 02:00 UTC calls
  the cleanup endpoint so anonymization is genuinely automatic, not lazy
- Switch masking window from first_activity_at to last_login_at (30 days
  of inactivity; resets on each login)
- Both auth pages: DSGVO footer now covers all Art. 13 requirements —
  data categories, retention cutoffs, Art. 15–21 rights, contact address,
  LfDI Baden-Württemberg as supervisory authority

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 15:11:51 +02:00

131 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { Suspense, useEffect, useState } from 'react'
import { useSearchParams, useRouter } from 'next/navigation'
import { motion } from 'framer-motion'
function VerifyContent() {
const searchParams = useSearchParams()
const router = useRouter()
const token = searchParams.get('token')
const [status, setStatus] = useState<'verifying' | 'success' | 'error'>('verifying')
const [errorMsg, setErrorMsg] = useState('')
useEffect(() => {
if (!token) {
setStatus('error')
setErrorMsg('No access token provided.')
return
}
async function verify() {
try {
// If the investor already has a valid session, skip token verification
const sessionCheck = await fetch('/api/auth/me')
if (sessionCheck.ok) {
router.push('/')
return
}
const res = await fetch('/api/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
})
if (res.ok) {
setStatus('success')
setTimeout(() => router.push('/'), 1000)
} else {
const data = await res.json()
setStatus('error')
setErrorMsg(data.error || 'Verification failed.')
}
} catch {
setStatus('error')
setErrorMsg('Network error. Please try again.')
}
}
verify()
}, [token, router])
return (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.4 }}
className="relative z-10 text-center max-w-md mx-auto px-6"
>
<div className="bg-white/[0.03] border border-white/[0.06] rounded-2xl p-8 backdrop-blur-sm">
{status === 'verifying' && (
<>
<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/60">Verifying your access link...</p>
</>
)}
{status === 'success' && (
<>
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-green-500/10 flex items-center justify-center">
<svg className="w-8 h-8 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
<p className="text-white/80 font-medium">Access verified!</p>
<p className="text-white/40 text-sm mt-2">Redirecting to pitch deck...</p>
</>
)}
{status === 'error' && (
<>
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-red-500/10 flex items-center justify-center">
<svg className="w-8 h-8 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<p className="text-white/80 font-medium mb-2">Access Denied</p>
<p className="text-white/50 text-sm">{errorMsg}</p>
<a
href="/auth"
className="inline-block mt-6 text-indigo-400 text-sm hover:text-indigo-300 transition-colors"
>
Back to login
</a>
</>
)}
</div>
</motion.div>
)
}
export default function VerifyPage() {
return (
<div className="h-screen flex items-center justify-center bg-[#0a0a1a] relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-indigo-950/30 via-transparent to-purple-950/20" />
<Suspense
fallback={
<div className="relative z-10 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/60">Loading...</p>
</div>
}
>
<VerifyContent />
</Suspense>
{/* Privacy Notice Footer */}
<div className="absolute bottom-0 left-0 right-0 z-10 px-8 py-4 border-t border-white/5">
<div className="max-w-2xl mx-auto">
<p className="text-[10px] text-white/20 leading-relaxed text-center">
<strong className="text-white/25">Datenschutzhinweis (Art. 13 DSGVO):</strong> Beim Zugriff werden technische Zugriffsdaten (IP-Adresse, Zeitpunkt, Browser) sowie  soweit eingeladen  personenbezogene Kontaktdaten (E-Mail, Name, Unternehmen) verarbeitet. Zweck: Zugangsverwaltung und Missbrauchsprävention. Rechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse). Speicherdauer: max. 30 Tage nach letztem Zugriff; nicht aktivierte Zugänge nach 90 Tagen. Danach automatische Anonymisierung. Ihre Rechte gem. Art. 1521 DSGVO (Auskunft, Berichtigung, Löschung, Einschränkung, Datenübertragbarkeit, Widerspruch): Anfragen an pitch@breakpilot.ai. Beschwerderecht bei der Aufsichtsbehörde: LfDI Baden-Württemberg (www.baden-wuerttemberg.datenschutz.de).</p>
<p className="text-[10px] text-white/15 text-center mt-1">
Verantwortlich: Benjamin Bönisch & Sharang Parnerkar · Kontakt: info@breakpilot.com
</p>
</div>
</div>
</div>
)
}