feat: Upselling-Funnel Assessment → Compliance Optimizer
Some checks failed
Build + Deploy / build-admin-compliance (push) Successful in 2m17s
Build + Deploy / build-backend-compliance (push) Successful in 3m22s
Build + Deploy / build-ai-sdk (push) Successful in 1m1s
Build + Deploy / build-developer-portal (push) Successful in 1m21s
Build + Deploy / build-tts (push) Failing after 1m32s
Build + Deploy / build-document-crawler (push) Successful in 37s
Build + Deploy / build-dsms-gateway (push) Successful in 24s
CI / branch-name (push) Has been skipped
Build + Deploy / trigger-orca (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m55s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Failing after 59s
CI / test-python-backend (push) Successful in 42s
CI / test-python-document-crawler (push) Successful in 35s
CI / test-python-dsms-gateway (push) Successful in 26s
CI / validate-canonical-controls (push) Successful in 18s

Verbindet das kostenlose UCCA Assessment mit dem bezahlten
Compliance Optimizer durch gezielte CTAs:

- OptimizerUpsellCard: Kontextabhaengig (CONDITIONAL→prominent, YES→dezent)
- Assessment Detail: "Optimieren" Button + CTA-Block nach Ergebnis
- Advisory Board ResultView: CTA nach Wizard-Abschluss
- Optimizer "new": Auto-Submit bei ?from_assessment={id}
- Optimizer Liste + Detail: Links zum Quell-Assessment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-23 12:28:49 +02:00
parent f8fd329059
commit 5d53acf5dc
6 changed files with 148 additions and 3 deletions

View File

@@ -41,6 +41,11 @@ export default function OptimizationDetailPage() {
<Link href="/sdk/compliance-optimizer" className="text-sm text-blue-600 hover:underline"> Zurueck</Link>
<h1 className="text-2xl font-bold text-gray-900 mt-1">{data.title || 'Optimierung'}</h1>
<p className="text-sm text-gray-500">{new Date(data.created_at).toLocaleString('de-DE')} v{data.constraint_version}</p>
{data.assessment_id && (
<Link href={`/sdk/use-cases/${data.assessment_id}`} className="text-sm text-purple-600 hover:underline">
Basierend auf Assessment
</Link>
)}
</div>
<ZoneBadge zone={data.is_compliant ? 'SAFE' : 'FORBIDDEN'} />
</div>

View File

@@ -1,7 +1,7 @@
'use client'
import React, { useState } from 'react'
import { useRouter } from 'next/navigation'
import React, { useState, useEffect, Suspense } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import { ZoneBadge } from '@/components/sdk/compliance-optimizer/ZoneBadge'
interface DimensionField {
@@ -63,9 +63,21 @@ const TOGGLE_DIMENSIONS = [
{ key: 'logging_required', label: 'Protokollierungspflicht' },
]
export default function NewOptimizationPage() {
function NewOptimizationPageInner() {
const router = useRouter()
const searchParams = useSearchParams()
const fromAssessment = searchParams.get('from_assessment')
const [autoOptimizing, setAutoOptimizing] = useState(false)
const [title, setTitle] = useState('')
useEffect(() => {
if (!fromAssessment) return
setAutoOptimizing(true)
fetch(`/api/sdk/v1/maximizer/optimize-from-assessment/${fromAssessment}`, { method: 'POST' })
.then(r => r.ok ? r.json() : Promise.reject('failed'))
.then(data => router.push(`/sdk/compliance-optimizer/${data.id}`))
.catch(() => setAutoOptimizing(false))
}, [fromAssessment, router])
const [config, setConfig] = useState<Record<string, string>>({
automation_level: 'assistive', decision_binding: 'non_binding', decision_impact: 'low',
domain: 'general', data_type: 'non_personal', human_in_loop: 'required',
@@ -104,6 +116,18 @@ export default function NewOptimizationPage() {
}
}
if (autoOptimizing) {
return (
<div className="max-w-4xl mx-auto p-6 text-center py-24">
<div className="animate-pulse">
<span className="text-4xl">📊</span>
<h2 className="text-xl font-bold text-gray-900 mt-4 mb-2">Optimierung laeuft...</h2>
<p className="text-sm text-gray-500">Assessment wird analysiert und optimale Konfiguration berechnet.</p>
</div>
</div>
)
}
return (
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-2xl font-bold text-gray-900 mb-1">Neue Optimierung</h1>
@@ -161,3 +185,11 @@ export default function NewOptimizationPage() {
</div>
)
}
export default function NewOptimizationPage() {
return (
<Suspense fallback={<div className="max-w-4xl mx-auto p-6 text-gray-500">Laden...</div>}>
<NewOptimizationPageInner />
</Suspense>
)
}

View File

@@ -12,6 +12,7 @@ interface OptimizationSummary {
created_at: string
zone_map: Record<string, { zone: 'FORBIDDEN' | 'RESTRICTED' | 'SAFE' }>
max_safe_config?: { safety_score: number; utility_score: number }
assessment_id?: string
}
function countZones(zoneMap: Record<string, { zone: string }>) {
@@ -83,6 +84,7 @@ export default function ComplianceOptimizerPage() {
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Titel</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Zonen</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Quelle</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Datum</th>
</tr>
</thead>
@@ -104,6 +106,15 @@ export default function ComplianceOptimizerPage() {
{zones.restricted > 0 && <span className="text-yellow-600 mr-2">{zones.restricted} eingeschraenkt</span>}
<span className="text-green-600">{zones.safe} erlaubt</span>
</td>
<td className="px-4 py-3 text-sm">
{o.assessment_id ? (
<Link href={`/sdk/use-cases/${o.assessment_id}`} className="text-purple-600 hover:underline text-xs">
Assessment
</Link>
) : (
<span className="text-gray-400 text-xs">Manuell</span>
)}
</td>
<td className="px-4 py-3 text-sm text-gray-500">
{new Date(o.created_at).toLocaleDateString('de-DE')}
</td>