refactor: Admin-Layout komplett entfernt — SDK als einziges Layout
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-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 19s
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-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 19s
Kaputtes (admin) Layout geloescht (Role-Selection, 404-Sidebar, localhost-Dashboard). SDK-Flow nach /sdk/sdk-flow verschoben. Route-Gruppe (sdk) aufgeloest. Root-Seite redirected auf /sdk. ~25 ungenutzte Dateien/Verzeichnisse entfernt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
414
admin-compliance/app/sdk/einwilligungen/privacy-policy/page.tsx
Normal file
414
admin-compliance/app/sdk/einwilligungen/privacy-policy/page.tsx
Normal file
@@ -0,0 +1,414 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Privacy Policy Generator Seite
|
||||
*
|
||||
* Generiert Datenschutzerklaerungen aus dem Datenpunktkatalog.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useSDK } from '@/lib/sdk'
|
||||
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader'
|
||||
import { PrivacyPolicyPreview } from '@/components/sdk/einwilligungen'
|
||||
import {
|
||||
EinwilligungenProvider,
|
||||
useEinwilligungen,
|
||||
} from '@/lib/sdk/einwilligungen/context'
|
||||
import {
|
||||
PREDEFINED_DATA_POINTS,
|
||||
} from '@/lib/sdk/einwilligungen/catalog/loader'
|
||||
import {
|
||||
generatePrivacyPolicy,
|
||||
} from '@/lib/sdk/einwilligungen/generator/privacy-policy'
|
||||
import {
|
||||
CompanyInfo,
|
||||
SupportedLanguage,
|
||||
ExportFormat,
|
||||
GeneratedPrivacyPolicy,
|
||||
} from '@/lib/sdk/einwilligungen/types'
|
||||
import {
|
||||
Building2,
|
||||
Mail,
|
||||
Phone,
|
||||
Globe,
|
||||
User,
|
||||
Save,
|
||||
ArrowLeft,
|
||||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
// =============================================================================
|
||||
// COMPANY INFO FORM
|
||||
// =============================================================================
|
||||
|
||||
interface CompanyInfoFormProps {
|
||||
companyInfo: CompanyInfo | null
|
||||
onChange: (info: CompanyInfo) => void
|
||||
}
|
||||
|
||||
function CompanyInfoForm({ companyInfo, onChange }: CompanyInfoFormProps) {
|
||||
const [formData, setFormData] = useState<CompanyInfo>(
|
||||
companyInfo || {
|
||||
name: '',
|
||||
address: '',
|
||||
city: '',
|
||||
postalCode: '',
|
||||
country: 'Deutschland',
|
||||
email: '',
|
||||
phone: '',
|
||||
website: '',
|
||||
dpoName: '',
|
||||
dpoEmail: '',
|
||||
dpoPhone: '',
|
||||
registrationNumber: '',
|
||||
vatId: '',
|
||||
}
|
||||
)
|
||||
|
||||
const handleChange = (field: keyof CompanyInfo, value: string) => {
|
||||
const updated = { ...formData, [field]: value }
|
||||
setFormData(updated)
|
||||
onChange(updated)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-6 space-y-6">
|
||||
<div className="flex items-center gap-3 border-b border-slate-200 pb-4">
|
||||
<Building2 className="w-5 h-5 text-slate-400" />
|
||||
<h3 className="font-semibold text-slate-900">Unternehmensdaten</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Company Name */}
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Firmenname *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) => handleChange('name', e.target.value)}
|
||||
placeholder="Muster GmbH"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Address */}
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Strasse & Hausnummer *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.address}
|
||||
onChange={(e) => handleChange('address', e.target.value)}
|
||||
placeholder="Musterstrasse 123"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Postal Code */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
PLZ *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.postalCode}
|
||||
onChange={(e) => handleChange('postalCode', e.target.value)}
|
||||
placeholder="12345"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* City */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Stadt *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.city}
|
||||
onChange={(e) => handleChange('city', e.target.value)}
|
||||
placeholder="Musterstadt"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Country */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Land
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.country}
|
||||
onChange={(e) => handleChange('country', e.target.value)}
|
||||
placeholder="Deutschland"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
E-Mail *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleChange('email', e.target.value)}
|
||||
placeholder="datenschutz@example.de"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Phone */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Telefon
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
value={formData.phone || ''}
|
||||
onChange={(e) => handleChange('phone', e.target.value)}
|
||||
placeholder="+49 123 456789"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Website */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Website
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
value={formData.website || ''}
|
||||
onChange={(e) => handleChange('website', e.target.value)}
|
||||
placeholder="https://example.de"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Registration Number */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Handelsregister
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.registrationNumber || ''}
|
||||
onChange={(e) => handleChange('registrationNumber', e.target.value)}
|
||||
placeholder="HRB 12345 Amtsgericht Musterstadt"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* VAT ID */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
USt-IdNr.
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.vatId || ''}
|
||||
onChange={(e) => handleChange('vatId', e.target.value)}
|
||||
placeholder="DE123456789"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* DPO Section */}
|
||||
<div className="border-t border-slate-200 pt-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<User className="w-5 h-5 text-slate-400" />
|
||||
<h4 className="font-medium text-slate-900">Datenschutzbeauftragter (optional)</h4>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.dpoName || ''}
|
||||
onChange={(e) => handleChange('dpoName', e.target.value)}
|
||||
placeholder="Max Mustermann"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
E-Mail
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={formData.dpoEmail || ''}
|
||||
onChange={(e) => handleChange('dpoEmail', e.target.value)}
|
||||
placeholder="dsb@example.de"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Telefon
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
value={formData.dpoPhone || ''}
|
||||
onChange={(e) => handleChange('dpoPhone', e.target.value)}
|
||||
placeholder="+49 123 456780"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PRIVACY POLICY CONTENT
|
||||
// =============================================================================
|
||||
|
||||
function PrivacyPolicyContent() {
|
||||
const { state } = useSDK()
|
||||
const {
|
||||
allDataPoints,
|
||||
state: einwilligungenState,
|
||||
} = useEinwilligungen()
|
||||
|
||||
const [companyInfo, setCompanyInfo] = useState<CompanyInfo | null>(null)
|
||||
const [language, setLanguage] = useState<SupportedLanguage>('de')
|
||||
const [format, setFormat] = useState<ExportFormat>('HTML')
|
||||
const [policy, setPolicy] = useState<GeneratedPrivacyPolicy | null>(null)
|
||||
const [isGenerating, setIsGenerating] = useState(false)
|
||||
|
||||
const handleGenerate = async () => {
|
||||
if (!companyInfo || !companyInfo.name || !companyInfo.email || !companyInfo.address) {
|
||||
alert('Bitte fuellen Sie zuerst die Pflichtfelder (Firmenname, Adresse, E-Mail) aus.')
|
||||
return
|
||||
}
|
||||
|
||||
setIsGenerating(true)
|
||||
|
||||
try {
|
||||
// Generate locally (could also call API)
|
||||
const generatedPolicy = generatePrivacyPolicy(
|
||||
state.tenantId || 'demo',
|
||||
allDataPoints,
|
||||
companyInfo,
|
||||
language,
|
||||
format
|
||||
)
|
||||
setPolicy(generatedPolicy)
|
||||
} catch (error) {
|
||||
console.error('Error generating policy:', error)
|
||||
alert('Fehler beim Generieren der Datenschutzerklaerung')
|
||||
} finally {
|
||||
setIsGenerating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDownload = (downloadFormat: ExportFormat) => {
|
||||
if (!policy?.content) return
|
||||
|
||||
const blob = new Blob([policy.content], {
|
||||
type: downloadFormat === 'HTML' ? 'text/html' : 'text/markdown',
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `datenschutzerklaerung-${language}.${downloadFormat === 'HTML' ? 'html' : 'md'}`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Back Link */}
|
||||
<Link
|
||||
href="/sdk/einwilligungen/catalog"
|
||||
className="inline-flex items-center gap-2 text-sm text-slate-600 hover:text-slate-900"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
Zurueck zum Katalog
|
||||
</Link>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900">
|
||||
Datenschutzerklaerung Generator
|
||||
</h1>
|
||||
<p className="text-slate-600 mt-1">
|
||||
Generieren Sie eine DSGVO-konforme Datenschutzerklaerung aus Ihrem Datenpunktkatalog.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-4">
|
||||
<div className="text-sm text-slate-500">Datenpunkte</div>
|
||||
<div className="text-2xl font-bold text-slate-900">{allDataPoints.length}</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-4">
|
||||
<div className="text-sm text-slate-500">Kategorien</div>
|
||||
<div className="text-2xl font-bold text-indigo-600">8</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-4">
|
||||
<div className="text-sm text-slate-500">Abschnitte</div>
|
||||
<div className="text-2xl font-bold text-purple-600">9</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl border border-slate-200 p-4">
|
||||
<div className="text-sm text-slate-500">Sprachen</div>
|
||||
<div className="text-2xl font-bold text-green-600">2</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Two Column Layout */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Left: Company Info */}
|
||||
<div>
|
||||
<CompanyInfoForm companyInfo={companyInfo} onChange={setCompanyInfo} />
|
||||
</div>
|
||||
|
||||
{/* Right: Preview */}
|
||||
<div>
|
||||
<PrivacyPolicyPreview
|
||||
policy={policy}
|
||||
isLoading={isGenerating}
|
||||
language={language}
|
||||
format={format}
|
||||
onLanguageChange={setLanguage}
|
||||
onFormatChange={setFormat}
|
||||
onGenerate={handleGenerate}
|
||||
onDownload={handleDownload}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MAIN PAGE
|
||||
// =============================================================================
|
||||
|
||||
export default function PrivacyPolicyPage() {
|
||||
return (
|
||||
<EinwilligungenProvider>
|
||||
<PrivacyPolicyContent />
|
||||
</EinwilligungenProvider>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user