Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
862
admin-compliance/app/(sdk)/sdk/company-profile/page.tsx
Normal file
862
admin-compliance/app/(sdk)/sdk/company-profile/page.tsx
Normal file
@@ -0,0 +1,862 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useSDK } from '@/lib/sdk'
|
||||
import {
|
||||
CompanyProfile,
|
||||
BusinessModel,
|
||||
OfferingType,
|
||||
TargetMarket,
|
||||
CompanySize,
|
||||
LegalForm,
|
||||
BUSINESS_MODEL_LABELS,
|
||||
OFFERING_TYPE_LABELS,
|
||||
TARGET_MARKET_LABELS,
|
||||
COMPANY_SIZE_LABELS,
|
||||
SDKCoverageAssessment,
|
||||
} from '@/lib/sdk/types'
|
||||
|
||||
// =============================================================================
|
||||
// WIZARD STEPS
|
||||
// =============================================================================
|
||||
|
||||
const WIZARD_STEPS = [
|
||||
{ id: 1, name: 'Basisinfos', description: 'Firmenname und Rechtsform' },
|
||||
{ id: 2, name: 'Geschäftsmodell', description: 'B2B, B2C und Angebote' },
|
||||
{ id: 3, name: 'Firmengröße', description: 'Mitarbeiter und Umsatz' },
|
||||
{ id: 4, name: 'Standorte', description: 'Hauptsitz und Zielmärkte' },
|
||||
{ id: 5, name: 'Datenschutz', description: 'Rollen und KI-Nutzung' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// LEGAL FORMS
|
||||
// =============================================================================
|
||||
|
||||
const LEGAL_FORM_LABELS: Record<LegalForm, string> = {
|
||||
einzelunternehmen: 'Einzelunternehmen',
|
||||
gbr: 'GbR',
|
||||
ohg: 'OHG',
|
||||
kg: 'KG',
|
||||
gmbh: 'GmbH',
|
||||
ug: 'UG (haftungsbeschränkt)',
|
||||
ag: 'AG',
|
||||
gmbh_co_kg: 'GmbH & Co. KG',
|
||||
ev: 'e.V. (Verein)',
|
||||
stiftung: 'Stiftung',
|
||||
other: 'Sonstige',
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// INDUSTRIES
|
||||
// =============================================================================
|
||||
|
||||
const INDUSTRIES = [
|
||||
'Technologie / IT',
|
||||
'E-Commerce / Handel',
|
||||
'Finanzdienstleistungen',
|
||||
'Gesundheitswesen',
|
||||
'Bildung',
|
||||
'Beratung / Consulting',
|
||||
'Marketing / Agentur',
|
||||
'Produktion / Industrie',
|
||||
'Logistik / Transport',
|
||||
'Immobilien',
|
||||
'Sonstige',
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// HELPER: ASSESS SDK COVERAGE
|
||||
// =============================================================================
|
||||
|
||||
function assessSDKCoverage(profile: Partial<CompanyProfile>): SDKCoverageAssessment {
|
||||
const coveredRegulations: string[] = ['DSGVO', 'BDSG', 'TTDSG', 'AI Act']
|
||||
const partiallyCoveredRegulations: string[] = []
|
||||
const notCoveredRegulations: string[] = []
|
||||
const reasons: string[] = []
|
||||
const recommendations: string[] = []
|
||||
|
||||
// Check target markets
|
||||
const targetMarkets = profile.targetMarkets || []
|
||||
|
||||
if (targetMarkets.includes('worldwide')) {
|
||||
notCoveredRegulations.push('CCPA (Kalifornien)', 'LGPD (Brasilien)', 'POPIA (Südafrika)')
|
||||
reasons.push('Weltweiter Betrieb erfordert Kenntnisse lokaler Datenschutzgesetze')
|
||||
recommendations.push('Für außereuropäische Märkte empfehlen wir die Konsultation lokaler Rechtsanwälte')
|
||||
}
|
||||
|
||||
if (targetMarkets.includes('eu_uk')) {
|
||||
partiallyCoveredRegulations.push('UK GDPR', 'UK AI Framework')
|
||||
reasons.push('UK-Recht weicht nach Brexit teilweise von EU-Recht ab')
|
||||
recommendations.push('Prüfen Sie UK-spezifische Anpassungen Ihrer Datenschutzerklärung')
|
||||
}
|
||||
|
||||
// Check company size
|
||||
if (profile.companySize === 'enterprise' || profile.companySize === 'large') {
|
||||
coveredRegulations.push('NIS2')
|
||||
reasons.push('Als größeres Unternehmen können NIS2-Pflichten relevant sein')
|
||||
}
|
||||
|
||||
// Check offerings
|
||||
const offerings = profile.offerings || []
|
||||
if (offerings.includes('webshop')) {
|
||||
coveredRegulations.push('Fernabsatzrecht')
|
||||
recommendations.push('Widerrufsbelehrung und AGB-Generator sind im SDK enthalten')
|
||||
}
|
||||
|
||||
// Determine if fully covered
|
||||
const requiresLegalCounsel = notCoveredRegulations.length > 0 || targetMarkets.includes('worldwide')
|
||||
const isFullyCovered = !requiresLegalCounsel && notCoveredRegulations.length === 0
|
||||
|
||||
return {
|
||||
isFullyCovered,
|
||||
coveredRegulations,
|
||||
partiallyCoveredRegulations,
|
||||
notCoveredRegulations,
|
||||
requiresLegalCounsel,
|
||||
reasons,
|
||||
recommendations,
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// STEP COMPONENTS
|
||||
// =============================================================================
|
||||
|
||||
function StepBasicInfo({
|
||||
data,
|
||||
onChange,
|
||||
}: {
|
||||
data: Partial<CompanyProfile>
|
||||
onChange: (updates: Partial<CompanyProfile>) => void
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Firmenname <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.companyName || ''}
|
||||
onChange={e => onChange({ companyName: e.target.value })}
|
||||
placeholder="Ihre Firma GmbH"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Rechtsform <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={data.legalForm || ''}
|
||||
onChange={e => onChange({ legalForm: e.target.value as LegalForm })}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Bitte wählen...</option>
|
||||
{Object.entries(LEGAL_FORM_LABELS).map(([value, label]) => (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Branche</label>
|
||||
<select
|
||||
value={data.industry || ''}
|
||||
onChange={e => onChange({ industry: e.target.value })}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Bitte wählen...</option>
|
||||
{INDUSTRIES.map(industry => (
|
||||
<option key={industry} value={industry}>
|
||||
{industry}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Gründungsjahr</label>
|
||||
<input
|
||||
type="number"
|
||||
value={data.foundedYear || ''}
|
||||
onChange={e => onChange({ foundedYear: parseInt(e.target.value) || null })}
|
||||
placeholder="2020"
|
||||
min="1800"
|
||||
max={new Date().getFullYear()}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StepBusinessModel({
|
||||
data,
|
||||
onChange,
|
||||
}: {
|
||||
data: Partial<CompanyProfile>
|
||||
onChange: (updates: Partial<CompanyProfile>) => void
|
||||
}) {
|
||||
const toggleOffering = (offering: OfferingType) => {
|
||||
const current = data.offerings || []
|
||||
if (current.includes(offering)) {
|
||||
onChange({ offerings: current.filter(o => o !== offering) })
|
||||
} else {
|
||||
onChange({ offerings: [...current, offering] })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-4">
|
||||
Geschäftsmodell <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{Object.entries(BUSINESS_MODEL_LABELS).map(([value, label]) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => onChange({ businessModel: value as BusinessModel })}
|
||||
className={`p-4 rounded-xl border-2 text-center transition-all ${
|
||||
data.businessModel === value
|
||||
? 'border-purple-500 bg-purple-50 text-purple-700'
|
||||
: 'border-gray-200 hover:border-purple-300'
|
||||
}`}
|
||||
>
|
||||
<div className="text-2xl mb-2">
|
||||
{value === 'B2B' ? '🏢' : value === 'B2C' ? '👥' : '🏢👥'}
|
||||
</div>
|
||||
<div className="font-medium">{label}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-4">
|
||||
Was bieten Sie an? <span className="text-gray-400">(Mehrfachauswahl möglich)</span>
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{Object.entries(OFFERING_TYPE_LABELS).map(([value, { label, description }]) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => toggleOffering(value as OfferingType)}
|
||||
className={`p-4 rounded-xl border-2 text-left transition-all ${
|
||||
(data.offerings || []).includes(value as OfferingType)
|
||||
? 'border-purple-500 bg-purple-50'
|
||||
: 'border-gray-200 hover:border-purple-300'
|
||||
}`}
|
||||
>
|
||||
<div className="font-medium text-gray-900">{label}</div>
|
||||
<div className="text-sm text-gray-500">{description}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StepCompanySize({
|
||||
data,
|
||||
onChange,
|
||||
}: {
|
||||
data: Partial<CompanyProfile>
|
||||
onChange: (updates: Partial<CompanyProfile>) => void
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-4">
|
||||
Unternehmensgröße <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="space-y-3">
|
||||
{Object.entries(COMPANY_SIZE_LABELS).map(([value, label]) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => onChange({ companySize: value as CompanySize })}
|
||||
className={`w-full p-4 rounded-xl border-2 text-left transition-all ${
|
||||
data.companySize === value
|
||||
? 'border-purple-500 bg-purple-50'
|
||||
: 'border-gray-200 hover:border-purple-300'
|
||||
}`}
|
||||
>
|
||||
<div className="font-medium text-gray-900">{label}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Mitarbeiterzahl</label>
|
||||
<select
|
||||
value={data.employeeCount || ''}
|
||||
onChange={e => onChange({ employeeCount: e.target.value })}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Bitte wählen...</option>
|
||||
<option value="1-9">1-9 Mitarbeiter</option>
|
||||
<option value="10-49">10-49 Mitarbeiter</option>
|
||||
<option value="50-249">50-249 Mitarbeiter</option>
|
||||
<option value="250-999">250-999 Mitarbeiter</option>
|
||||
<option value="1000+">1.000+ Mitarbeiter</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Jahresumsatz</label>
|
||||
<select
|
||||
value={data.annualRevenue || ''}
|
||||
onChange={e => onChange({ annualRevenue: e.target.value })}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Bitte wählen...</option>
|
||||
<option value="< 2 Mio">< 2 Mio. Euro</option>
|
||||
<option value="2-10 Mio">2-10 Mio. Euro</option>
|
||||
<option value="10-50 Mio">10-50 Mio. Euro</option>
|
||||
<option value="> 50 Mio">> 50 Mio. Euro</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StepLocations({
|
||||
data,
|
||||
onChange,
|
||||
}: {
|
||||
data: Partial<CompanyProfile>
|
||||
onChange: (updates: Partial<CompanyProfile>) => void
|
||||
}) {
|
||||
const toggleMarket = (market: TargetMarket) => {
|
||||
const current = data.targetMarkets || []
|
||||
if (current.includes(market)) {
|
||||
onChange({ targetMarkets: current.filter(m => m !== market) })
|
||||
} else {
|
||||
onChange({ targetMarkets: [...current, market] })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Land des Hauptsitzes <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={data.headquartersCountry || ''}
|
||||
onChange={e => onChange({ headquartersCountry: e.target.value })}
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">Bitte wählen...</option>
|
||||
<option value="DE">Deutschland</option>
|
||||
<option value="AT">Österreich</option>
|
||||
<option value="CH">Schweiz</option>
|
||||
<option value="other">Anderes Land</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Stadt</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.headquartersCity || ''}
|
||||
onChange={e => onChange({ headquartersCity: e.target.value })}
|
||||
placeholder="z.B. Berlin"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-4">
|
||||
Zielmärkte <span className="text-red-500">*</span>
|
||||
<span className="text-gray-400 font-normal ml-2">Wo verkaufen/operieren Sie?</span>
|
||||
</label>
|
||||
<div className="space-y-3">
|
||||
{Object.entries(TARGET_MARKET_LABELS).map(([value, { label, description, regulations }]) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => toggleMarket(value as TargetMarket)}
|
||||
className={`w-full p-4 rounded-xl border-2 text-left transition-all ${
|
||||
(data.targetMarkets || []).includes(value as TargetMarket)
|
||||
? 'border-purple-500 bg-purple-50'
|
||||
: 'border-gray-200 hover:border-purple-300'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">{label}</div>
|
||||
<div className="text-sm text-gray-500">{description}</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-xs text-gray-400">Relevante Regulierungen:</div>
|
||||
<div className="text-xs text-purple-600">{regulations.join(', ')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StepDataProtection({
|
||||
data,
|
||||
onChange,
|
||||
}: {
|
||||
data: Partial<CompanyProfile>
|
||||
onChange: (updates: Partial<CompanyProfile>) => void
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-4">
|
||||
Datenschutz-Rolle nach DSGVO
|
||||
</label>
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-start gap-4 p-4 rounded-xl border-2 border-gray-200 hover:border-purple-300 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.isDataController ?? true}
|
||||
onChange={e => onChange({ isDataController: e.target.checked })}
|
||||
className="mt-1 w-5 h-5 text-purple-600 rounded focus:ring-purple-500"
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">Verantwortlicher (Art. 4 Nr. 7 DSGVO)</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
Wir entscheiden selbst über Zwecke und Mittel der Datenverarbeitung
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="flex items-start gap-4 p-4 rounded-xl border-2 border-gray-200 hover:border-purple-300 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.isDataProcessor ?? false}
|
||||
onChange={e => onChange({ isDataProcessor: e.target.checked })}
|
||||
className="mt-1 w-5 h-5 text-purple-600 rounded focus:ring-purple-500"
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">Auftragsverarbeiter (Art. 4 Nr. 8 DSGVO)</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
Wir verarbeiten personenbezogene Daten im Auftrag anderer Unternehmen
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="flex items-start gap-4 p-4 rounded-xl border-2 border-gray-200 hover:border-purple-300 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={data.usesAI ?? false}
|
||||
onChange={e => onChange({ usesAI: e.target.checked })}
|
||||
className="mt-1 w-5 h-5 text-purple-600 rounded focus:ring-purple-500"
|
||||
/>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900">Wir setzen KI/ML-Systeme ein</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
Chatbots, Empfehlungssysteme, automatisierte Entscheidungen, etc.
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Datenschutzbeauftragter (Name)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.dpoName || ''}
|
||||
onChange={e => onChange({ dpoName: e.target.value || null })}
|
||||
placeholder="Optional"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">DSB E-Mail</label>
|
||||
<input
|
||||
type="email"
|
||||
value={data.dpoEmail || ''}
|
||||
onChange={e => onChange({ dpoEmail: e.target.value || null })}
|
||||
placeholder="dsb@firma.de"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COVERAGE ASSESSMENT COMPONENT
|
||||
// =============================================================================
|
||||
|
||||
function CoverageAssessmentPanel({ profile }: { profile: Partial<CompanyProfile> }) {
|
||||
const assessment = assessSDKCoverage(profile)
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">SDK-Abdeckung</h3>
|
||||
|
||||
{/* Status */}
|
||||
<div
|
||||
className={`p-4 rounded-lg mb-4 ${
|
||||
assessment.isFullyCovered
|
||||
? 'bg-green-50 border border-green-200'
|
||||
: 'bg-amber-50 border border-amber-200'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{assessment.isFullyCovered ? (
|
||||
<>
|
||||
<svg
|
||||
className="w-5 h-5 text-green-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span className="font-medium text-green-800">Vollständig durch SDK abgedeckt</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg
|
||||
className="w-5 h-5 text-amber-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<span className="font-medium text-amber-800">Teilweise Einschränkungen</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Covered Regulations */}
|
||||
{assessment.coveredRegulations.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<div className="text-sm font-medium text-gray-700 mb-2">Abgedeckte Regulierungen</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{assessment.coveredRegulations.map(reg => (
|
||||
<span key={reg} className="px-2 py-1 bg-green-100 text-green-700 text-sm rounded-full">
|
||||
{reg}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Not Covered */}
|
||||
{assessment.notCoveredRegulations.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<div className="text-sm font-medium text-gray-700 mb-2">Nicht abgedeckt</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{assessment.notCoveredRegulations.map(reg => (
|
||||
<span key={reg} className="px-2 py-1 bg-red-100 text-red-700 text-sm rounded-full">
|
||||
{reg}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Recommendations */}
|
||||
{assessment.recommendations.length > 0 && (
|
||||
<div className="mt-4 p-4 bg-blue-50 rounded-lg">
|
||||
<div className="text-sm font-medium text-blue-800 mb-2">Empfehlungen</div>
|
||||
<ul className="text-sm text-blue-700 space-y-1">
|
||||
{assessment.recommendations.map((rec, i) => (
|
||||
<li key={i} className="flex items-start gap-2">
|
||||
<span>•</span>
|
||||
<span>{rec}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Legal Counsel Warning */}
|
||||
{assessment.requiresLegalCounsel && (
|
||||
<div className="mt-4 p-4 bg-amber-50 border border-amber-200 rounded-lg">
|
||||
<div className="flex items-start gap-3">
|
||||
<svg
|
||||
className="w-5 h-5 text-amber-600 mt-0.5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
<div>
|
||||
<div className="font-medium text-amber-800">Rechtsberatung empfohlen</div>
|
||||
<div className="text-sm text-amber-700 mt-1">
|
||||
Basierend auf Ihrem Profil empfehlen wir die Konsultation eines spezialisierten
|
||||
Rechtsanwalts für Bereiche, die über den Scope dieses SDKs hinausgehen.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MAIN COMPONENT
|
||||
// =============================================================================
|
||||
|
||||
export default function CompanyProfilePage() {
|
||||
const { state, dispatch, setCompanyProfile, goToNextStep } = useSDK()
|
||||
const [currentStep, setCurrentStep] = useState(1)
|
||||
const [formData, setFormData] = useState<Partial<CompanyProfile>>({
|
||||
companyName: '',
|
||||
legalForm: undefined,
|
||||
industry: '',
|
||||
foundedYear: null,
|
||||
businessModel: undefined,
|
||||
offerings: [],
|
||||
companySize: undefined,
|
||||
employeeCount: '',
|
||||
annualRevenue: '',
|
||||
headquartersCountry: 'DE',
|
||||
headquartersCity: '',
|
||||
hasInternationalLocations: false,
|
||||
internationalCountries: [],
|
||||
targetMarkets: [],
|
||||
primaryJurisdiction: 'DE',
|
||||
isDataController: true,
|
||||
isDataProcessor: false,
|
||||
usesAI: false,
|
||||
aiUseCases: [],
|
||||
dpoName: null,
|
||||
dpoEmail: null,
|
||||
legalContactName: null,
|
||||
legalContactEmail: null,
|
||||
isComplete: false,
|
||||
completedAt: null,
|
||||
})
|
||||
|
||||
// Load existing profile
|
||||
useEffect(() => {
|
||||
if (state.companyProfile) {
|
||||
setFormData(state.companyProfile)
|
||||
// If profile is complete, show last step
|
||||
if (state.companyProfile.isComplete) {
|
||||
setCurrentStep(5)
|
||||
}
|
||||
}
|
||||
}, [state.companyProfile])
|
||||
|
||||
const updateFormData = (updates: Partial<CompanyProfile>) => {
|
||||
setFormData(prev => ({ ...prev, ...updates }))
|
||||
}
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentStep < 5) {
|
||||
setCurrentStep(prev => prev + 1)
|
||||
} else {
|
||||
// Complete profile
|
||||
const completeProfile: CompanyProfile = {
|
||||
...formData,
|
||||
isComplete: true,
|
||||
completedAt: new Date(),
|
||||
} as CompanyProfile
|
||||
|
||||
setCompanyProfile(completeProfile)
|
||||
dispatch({ type: 'COMPLETE_STEP', payload: 'company-profile' })
|
||||
goToNextStep()
|
||||
}
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
if (currentStep > 1) {
|
||||
setCurrentStep(prev => prev - 1)
|
||||
}
|
||||
}
|
||||
|
||||
const canProceed = () => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return formData.companyName && formData.legalForm
|
||||
case 2:
|
||||
return formData.businessModel && (formData.offerings?.length || 0) > 0
|
||||
case 3:
|
||||
return formData.companySize
|
||||
case 4:
|
||||
return formData.headquartersCountry && (formData.targetMarkets?.length || 0) > 0
|
||||
case 5:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">Unternehmensprofil</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Helfen Sie uns, Ihr Unternehmen zu verstehen, damit wir die relevanten Regulierungen
|
||||
identifizieren können.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Progress Steps */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between">
|
||||
{WIZARD_STEPS.map((step, index) => (
|
||||
<React.Fragment key={step.id}>
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-full flex items-center justify-center text-sm font-medium ${
|
||||
step.id < currentStep
|
||||
? 'bg-purple-600 text-white'
|
||||
: step.id === currentStep
|
||||
? 'bg-purple-100 text-purple-600 border-2 border-purple-600'
|
||||
: 'bg-gray-100 text-gray-400'
|
||||
}`}
|
||||
>
|
||||
{step.id < currentStep ? (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
step.id
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-3 hidden sm:block">
|
||||
<div
|
||||
className={`text-sm font-medium ${
|
||||
step.id <= currentStep ? 'text-gray-900' : 'text-gray-400'
|
||||
}`}
|
||||
>
|
||||
{step.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{index < WIZARD_STEPS.length - 1 && (
|
||||
<div
|
||||
className={`flex-1 h-0.5 mx-4 ${
|
||||
step.id < currentStep ? 'bg-purple-600' : 'bg-gray-200'
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Form */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-8">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
{WIZARD_STEPS[currentStep - 1].name}
|
||||
</h2>
|
||||
<p className="text-gray-500">{WIZARD_STEPS[currentStep - 1].description}</p>
|
||||
</div>
|
||||
|
||||
{currentStep === 1 && <StepBasicInfo data={formData} onChange={updateFormData} />}
|
||||
{currentStep === 2 && <StepBusinessModel data={formData} onChange={updateFormData} />}
|
||||
{currentStep === 3 && <StepCompanySize data={formData} onChange={updateFormData} />}
|
||||
{currentStep === 4 && <StepLocations data={formData} onChange={updateFormData} />}
|
||||
{currentStep === 5 && <StepDataProtection data={formData} onChange={updateFormData} />}
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex justify-between mt-8 pt-6 border-t border-gray-200">
|
||||
<button
|
||||
onClick={handleBack}
|
||||
disabled={currentStep === 1}
|
||||
className="px-6 py-3 text-gray-600 hover:text-gray-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Zurück
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNext}
|
||||
disabled={!canProceed()}
|
||||
className="px-8 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{currentStep === 5 ? 'Profil speichern & weiter' : 'Weiter'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar: Coverage Assessment */}
|
||||
<div className="lg:col-span-1">
|
||||
<CoverageAssessmentPanel profile={formData} />
|
||||
|
||||
{/* Info Box */}
|
||||
<div className="mt-6 bg-blue-50 rounded-xl border border-blue-200 p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<svg
|
||||
className="w-5 h-5 text-blue-600 mt-0.5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<div>
|
||||
<div className="font-medium text-blue-800">Warum diese Fragen?</div>
|
||||
<div className="text-sm text-blue-700 mt-1">
|
||||
Diese Informationen helfen uns, die für Ihr Unternehmen relevanten Regulierungen
|
||||
zu identifizieren und ehrlich zu kommunizieren, wo unsere Grenzen liegen.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user