Files
breakpilot-core/pitch-deck/app/auth/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

139 lines
6.5 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 { motion } from 'framer-motion'
import { useState, FormEvent } from 'react'
type Status = 'idle' | 'submitting' | 'sent' | 'error'
export default function AuthPage() {
const [email, setEmail] = useState('')
const [status, setStatus] = useState<Status>('idle')
const [message, setMessage] = useState<string | null>(null)
async function handleSubmit(e: FormEvent) {
e.preventDefault()
if (!email.trim()) return
setStatus('submitting')
setMessage(null)
try {
const res = await fetch('/api/auth/request-link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email.trim() }),
})
const data = await res.json().catch(() => ({}))
if (res.ok) {
setStatus('sent')
setMessage(data.message || 'If this email was invited, a fresh access link has been sent.')
} else {
setStatus('error')
setMessage(data.error || 'Something went wrong. Please try again.')
}
} catch {
setStatus('error')
setMessage('Network error. Please try again.')
}
}
return (
<div className="h-screen flex items-center justify-center bg-[#0a0a1a] relative overflow-hidden">
{/* Background gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-indigo-950/30 via-transparent to-purple-950/20" />
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="relative z-10 text-center max-w-md mx-auto px-6"
>
<div className="mb-8">
<h1 className="text-3xl font-bold bg-gradient-to-r from-indigo-400 to-purple-400 bg-clip-text text-transparent mb-2">
BreakPilot ComplAI
</h1>
<p className="text-white/30 text-sm">Investor Pitch Deck</p>
</div>
<div className="bg-white/[0.03] border border-white/[0.06] rounded-2xl p-8 backdrop-blur-sm">
<div className="w-16 h-16 mx-auto mb-6 rounded-full bg-indigo-500/10 flex items-center justify-center">
<svg className="w-8 h-8 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M21.75 9v.906a2.25 2.25 0 01-1.183 1.981l-6.478 3.488M2.25 9v.906a2.25 2.25 0 001.183 1.981l6.478 3.488m8.839 2.51l-4.66-2.51m0 0l-1.023-.55a2.25 2.25 0 00-2.134 0l-1.022.55m0 0l-4.661 2.51m16.5-1.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75A2.25 2.25 0 014.5 4.5h15a2.25 2.25 0 012.25 2.25v11.25z" />
</svg>
</div>
<h2 className="text-xl font-semibold text-white/90 mb-3">
Invitation Required
</h2>
<p className="text-white/50 text-sm leading-relaxed mb-6">
This interactive pitch deck is available by invitation only.
If you were invited, enter your email below and we&apos;ll send you a fresh access link.
</p>
{status === 'sent' ? (
<div className="text-left bg-indigo-500/10 border border-indigo-500/20 rounded-lg p-4 mb-5">
<p className="text-indigo-200/90 text-sm leading-relaxed">
{message}
</p>
</div>
) : (
<form onSubmit={handleSubmit} className="text-left mb-5">
<label htmlFor="email" className="block text-white/60 text-xs mb-2 uppercase tracking-wide">
Email address
</label>
<input
id="email"
type="email"
required
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={status === 'submitting'}
placeholder="you@example.com"
className="w-full bg-white/[0.04] border border-white/[0.08] rounded-lg px-4 py-3 text-white/90 text-sm placeholder:text-white/20 focus:outline-none focus:border-indigo-400/50 focus:bg-white/[0.06] transition-colors disabled:opacity-50"
/>
{status === 'error' && message && (
<p className="mt-2 text-rose-300/80 text-xs">{message}</p>
)}
<button
type="submit"
disabled={status === 'submitting' || !email.trim()}
className="w-full mt-4 bg-gradient-to-br from-indigo-500 to-purple-500 hover:from-indigo-400 hover:to-purple-400 disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm font-medium rounded-lg px-4 py-3 transition-all"
>
{status === 'submitting' ? 'Sending…' : 'Send access link'}
</button>
</form>
)}
<div className="border-t border-white/[0.06] pt-5">
<p className="text-white/30 text-xs">
Questions? Contact us at{' '}
<a href="mailto:pitch@breakpilot.ai" className="text-indigo-400/80 hover:text-indigo-400 transition-colors">
pitch@breakpilot.ai
</a>
</p>
</div>
</div>
<p className="mt-6 text-white/20 text-xs">
We are an AI-first company. No PDFs. No slide decks. Just code.
</p>
</motion.div>
{/* Privacy Notice Footer */}
<div className="absolute bottom-0 left-0 right-0 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>
)
}