This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/admin-v2/components/sdk/dsr/DSRIdentityModal.tsx
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +01:00

264 lines
9.8 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import {
IdentityVerificationMethod,
DSRVerifyIdentityRequest
} from '@/lib/sdk/dsr/types'
interface DSRIdentityModalProps {
isOpen: boolean
onClose: () => void
onVerify: (verification: DSRVerifyIdentityRequest) => Promise<void>
requesterName: string
requesterEmail: string
}
const VERIFICATION_METHODS: {
value: IdentityVerificationMethod
label: string
description: string
icon: string
}[] = [
{
value: 'id_document',
label: 'Ausweisdokument',
description: 'Kopie von Personalausweis oder Reisepass',
icon: 'M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0m-5 8a2 2 0 100-4 2 2 0 000 4zm0 0c1.306 0 2.417.835 2.83 2M9 14a3.001 3.001 0 00-2.83 2M15 11h3m-3 4h2'
},
{
value: 'email',
label: 'E-Mail-Bestaetigung',
description: 'Bestaetigung ueber verifizierte E-Mail-Adresse',
icon: 'M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z'
},
{
value: 'existing_account',
label: 'Bestehendes Konto',
description: 'Anmeldung ueber bestehendes Kundenkonto',
icon: 'M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z'
},
{
value: 'phone',
label: 'Telefonische Bestaetigung',
description: 'Verifizierung per Telefonanruf',
icon: 'M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z'
},
{
value: 'postal',
label: 'Postalische Bestaetigung',
description: 'Bestaetigung per Brief',
icon: 'M3 19v-8.93a2 2 0 01.89-1.664l7-4.666a2 2 0 012.22 0l7 4.666A2 2 0 0121 10.07V19M3 19a2 2 0 002 2h14a2 2 0 002-2M3 19l6.75-4.5M21 19l-6.75-4.5M3 10l6.75 4.5M21 10l-6.75 4.5m0 0l-1.14.76a2 2 0 01-2.22 0l-1.14-.76'
},
{
value: 'other',
label: 'Sonstige Methode',
description: 'Andere Verifizierungsmethode',
icon: 'M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z'
}
]
export function DSRIdentityModal({
isOpen,
onClose,
onVerify,
requesterName,
requesterEmail
}: DSRIdentityModalProps) {
const [selectedMethod, setSelectedMethod] = useState<IdentityVerificationMethod | null>(null)
const [notes, setNotes] = useState('')
const [documentRef, setDocumentRef] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
if (!isOpen) return null
const handleVerify = async () => {
if (!selectedMethod) {
setError('Bitte waehlen Sie eine Verifizierungsmethode')
return
}
setIsLoading(true)
setError(null)
try {
await onVerify({
method: selectedMethod,
notes: notes || undefined,
documentRef: documentRef || undefined
})
onClose()
} catch (err) {
setError(err instanceof Error ? err.message : 'Verifizierung fehlgeschlagen')
} finally {
setIsLoading(false)
}
}
const handleClose = () => {
setSelectedMethod(null)
setNotes('')
setDocumentRef('')
setError(null)
onClose()
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/50"
onClick={handleClose}
/>
{/* Modal */}
<div className="relative bg-white rounded-2xl shadow-xl max-w-lg w-full mx-4 max-h-[90vh] overflow-hidden">
{/* Header */}
<div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-gray-900">
Identitaet verifizieren
</h2>
<button
onClick={handleClose}
className="p-2 text-gray-400 hover:text-gray-600 rounded-lg hover:bg-gray-100"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
{/* Content */}
<div className="px-6 py-4 overflow-y-auto max-h-[60vh]">
{/* Requester Info */}
<div className="bg-gray-50 rounded-xl p-4 mb-6">
<div className="text-sm text-gray-500 mb-1">Antragsteller</div>
<div className="font-medium text-gray-900">{requesterName}</div>
<div className="text-sm text-gray-600">{requesterEmail}</div>
</div>
{/* Error */}
{error && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
{error}
</div>
)}
{/* Verification Methods */}
<div className="space-y-2 mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Verifizierungsmethode
</label>
{VERIFICATION_METHODS.map(method => (
<button
key={method.value}
onClick={() => setSelectedMethod(method.value)}
className={`
w-full flex items-start gap-3 p-3 rounded-xl border-2 text-left transition-all
${selectedMethod === method.value
? 'border-purple-500 bg-purple-50'
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
}
`}
>
<div className={`
w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0
${selectedMethod === method.value ? 'bg-purple-100' : 'bg-gray-100'}
`}>
<svg
className={`w-5 h-5 ${selectedMethod === method.value ? 'text-purple-600' : 'text-gray-500'}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={method.icon} />
</svg>
</div>
<div className="flex-1 min-w-0">
<div className={`font-medium ${selectedMethod === method.value ? 'text-purple-700' : 'text-gray-900'}`}>
{method.label}
</div>
<div className="text-sm text-gray-500">
{method.description}
</div>
</div>
{selectedMethod === method.value && (
<svg className="w-5 h-5 text-purple-600 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
)}
</button>
))}
</div>
{/* Document Reference */}
{selectedMethod === 'id_document' && (
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Dokumentenreferenz (optional)
</label>
<input
type="text"
value={documentRef}
onChange={(e) => setDocumentRef(e.target.value)}
placeholder="z.B. Datei-ID oder Speicherort"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
/>
</div>
)}
{/* Notes */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Notizen (optional)
</label>
<textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="Weitere Informationen zur Verifizierung..."
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 resize-none"
/>
</div>
</div>
{/* Footer */}
<div className="px-6 py-4 border-t border-gray-200 bg-gray-50 flex items-center justify-end gap-3">
<button
onClick={handleClose}
className="px-4 py-2 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors"
>
Abbrechen
</button>
<button
onClick={handleVerify}
disabled={!selectedMethod || isLoading}
className={`
px-4 py-2 rounded-lg font-medium transition-colors
${selectedMethod && !isLoading
? 'bg-green-600 text-white hover:bg-green-700'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}
`}
>
{isLoading ? (
<span className="flex items-center gap-2">
<svg className="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
Verifiziere...
</span>
) : (
'Identitaet bestaetigen'
)}
</button>
</div>
</div>
</div>
)
}