Files
breakpilot-lehrer/studio-v2/components/B2BMigrationWizard.tsx
Benjamin Admin b4613e26f3 [split-required] Split 500-850 LOC files (batch 2)
backend-lehrer (10 files):
- game/database.py (785 → 5), correction_api.py (683 → 4)
- classroom_engine/antizipation.py (676 → 5)
- llm_gateway schools/edu_search already done in prior batch

klausur-service (12 files):
- orientation_crop_api.py (694 → 5), pdf_export.py (677 → 4)
- zeugnis_crawler.py (676 → 5), grid_editor_api.py (671 → 5)
- eh_templates.py (658 → 5), mail/api.py (651 → 5)
- qdrant_service.py (638 → 5), training_api.py (625 → 4)

website (6 pages):
- middleware (696 → 8), mail (733 → 6), consent (628 → 8)
- compliance/risks (622 → 5), export (502 → 5), brandbook (629 → 7)

studio-v2 (3 components):
- B2BMigrationWizard (848 → 3), CleanupPanel (765 → 2)
- dashboard-experimental (739 → 2)

admin-lehrer (4 files):
- uebersetzungen (769 → 4), manager (670 → 2)
- ChunkBrowserQA (675 → 6), dsfa/page (674 → 5)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 08:24:01 +02:00

309 lines
11 KiB
TypeScript

'use client'
import { useState } from 'react'
import { useTheme } from '@/lib/ThemeContext'
import { useAlertsB2B, Package } from '@/lib/AlertsB2BContext'
import { WizardStep1, WizardStep2, WizardStep3 } from './B2BWizardSteps'
import { WizardStep4, WizardStep5 } from './B2BWizardDetails'
import type { MigrationMethod } from './B2BWizardSteps'
interface B2BMigrationWizardProps {
onComplete: () => void
onSkip?: () => void
onCancel?: () => void
}
export function B2BMigrationWizard({ onComplete, onSkip, onCancel }: B2BMigrationWizardProps) {
const { isDark } = useTheme()
const {
tenant,
updateTenant,
settings,
updateSettings,
availableTemplates,
selectTemplate,
generateInboundEmail,
addSource
} = useAlertsB2B()
const [step, setStep] = useState(1)
const [migrationMethod, setMigrationMethod] = useState<MigrationMethod>(null)
const [companyName, setCompanyName] = useState(tenant.companyName || '')
const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null)
const [inboundEmail, setInboundEmail] = useState('')
const [rssUrls, setRssUrls] = useState<string[]>([''])
const [alertDescription, setAlertDescription] = useState('')
const [testEmailSent, setTestEmailSent] = useState(false)
const [selectedRegions, setSelectedRegions] = useState<string[]>(['EUROPE'])
const [selectedPackages, setSelectedPackages] = useState<string[]>(['PARKING', 'EV_CHARGING'])
const totalSteps = 5
const handleNext = () => {
if (step < totalSteps) {
if (step === 1 && companyName.trim()) {
updateTenant({ companyName: companyName.trim() })
}
if (step === 2 && selectedTemplateId) {
selectTemplate(selectedTemplateId)
}
if (step === 3 && migrationMethod === 'email' && !inboundEmail) {
setInboundEmail(generateInboundEmail())
}
setStep(step + 1)
} else {
completeWizard()
}
}
const handleBack = () => {
if (step > 1) {
setStep(step - 1)
}
}
const completeWizard = () => {
if (migrationMethod === 'email' && inboundEmail) {
addSource({
tenantId: tenant.id,
type: 'email',
inboundAddress: inboundEmail,
label: 'Google Alerts Weiterleitung',
active: true
})
} else if (migrationMethod === 'rss') {
rssUrls.filter(url => url.trim()).forEach((url, idx) => {
addSource({
tenantId: tenant.id,
type: 'rss',
rssUrl: url.trim(),
label: `RSS Feed ${idx + 1}`,
active: true
})
})
}
updateSettings({
migrationCompleted: true,
wizardCompleted: true,
selectedRegions,
selectedPackages: selectedPackages as any[]
})
onComplete()
}
const canProceed = () => {
switch (step) {
case 1:
return companyName.trim().length > 0
case 2:
return selectedTemplateId !== null
case 3:
return migrationMethod !== null
case 4:
if (migrationMethod === 'email') return inboundEmail.length > 0
if (migrationMethod === 'rss') return rssUrls.some(url => url.trim().length > 0)
if (migrationMethod === 'reconstruct') return alertDescription.trim().length > 10
return true
case 5:
return true
default:
return false
}
}
const selectedTemplate = availableTemplates.find(t => t.templateId === selectedTemplateId)
return (
<div className={`min-h-screen flex flex-col ${
isDark
? 'bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-800'
: 'bg-gradient-to-br from-slate-100 via-blue-50 to-indigo-100'
}`}>
{/* Animated Background Blobs */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className={`absolute -top-40 -right-40 w-80 h-80 rounded-full mix-blend-multiply filter blur-3xl animate-blob ${
isDark ? 'bg-purple-500 opacity-70' : 'bg-purple-300 opacity-50'
}`} />
<div className={`absolute -bottom-40 -left-40 w-80 h-80 rounded-full mix-blend-multiply filter blur-3xl animate-blob animation-delay-2000 ${
isDark ? 'bg-blue-500 opacity-70' : 'bg-blue-300 opacity-50'
}`} />
<div className={`absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-80 h-80 rounded-full mix-blend-multiply filter blur-3xl animate-blob animation-delay-4000 ${
isDark ? 'bg-pink-500 opacity-70' : 'bg-pink-300 opacity-50'
}`} />
</div>
{/* Blob Animation Styles */}
<style jsx>{`
@keyframes blob {
0% { transform: translate(0px, 0px) scale(1); }
33% { transform: translate(30px, -50px) scale(1.1); }
66% { transform: translate(-20px, 20px) scale(0.9); }
100% { transform: translate(0px, 0px) scale(1); }
}
.animate-blob {
animation: blob 7s infinite;
}
.animation-delay-2000 {
animation-delay: 2s;
}
.animation-delay-4000 {
animation-delay: 4s;
}
`}</style>
<div className="relative z-10 flex-1 flex flex-col items-center justify-center p-8">
{/* Exit Button */}
{onCancel && (
<button
onClick={onCancel}
className={`fixed top-6 right-6 z-50 flex items-center gap-2 px-4 py-2 rounded-2xl backdrop-blur-xl border transition-all ${
isDark
? 'bg-white/10 border-white/20 text-white/70 hover:bg-white/20 hover:text-white'
: 'bg-white/70 border-black/10 text-slate-600 hover:bg-white hover:text-slate-900 shadow-lg'
}`}
>
<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>
<span className="text-sm font-medium">Abbrechen</span>
</button>
)}
{/* Header */}
<div className="text-center mb-8">
<div className="flex items-center justify-center gap-3 mb-4">
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-3xl shadow-lg shadow-purple-500/30">
🏢
</div>
<div className="text-left">
<h1 className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>
B2B Alerts einrichten
</h1>
<p className={`text-sm ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
Bringen Sie Ihre bestehenden Google Alerts mit
</p>
</div>
</div>
</div>
{/* Progress Bar */}
<div className="w-full max-w-3xl mb-8">
<div className="flex items-center justify-between mb-2">
{[1, 2, 3, 4, 5].map((s) => (
<div
key={s}
className={`w-10 h-10 rounded-full flex items-center justify-center font-medium transition-all ${
s === step
? 'bg-gradient-to-br from-purple-500 to-pink-500 text-white scale-110 shadow-lg shadow-purple-500/30'
: s < step
? isDark
? 'bg-green-500/30 text-green-300'
: 'bg-green-100 text-green-700'
: isDark
? 'bg-white/10 text-white/40'
: 'bg-slate-200 text-slate-400'
}`}
>
{s < step ? '✓' : s}
</div>
))}
</div>
<div className={`h-2 rounded-full overflow-hidden ${isDark ? 'bg-white/10' : 'bg-slate-200'}`}>
<div
className="h-full bg-gradient-to-r from-purple-500 to-pink-500 transition-all duration-500"
style={{ width: `${((step - 1) / (totalSteps - 1)) * 100}%` }}
/>
</div>
</div>
{/* Main Card */}
<div className={`w-full max-w-3xl backdrop-blur-xl border rounded-3xl p-8 ${
isDark
? 'bg-white/10 border-white/20'
: 'bg-white/80 border-black/10 shadow-xl'
}`}>
{step === 1 && (
<WizardStep1 companyName={companyName} setCompanyName={setCompanyName} />
)}
{step === 2 && (
<WizardStep2
availableTemplates={availableTemplates}
selectedTemplateId={selectedTemplateId}
setSelectedTemplateId={setSelectedTemplateId}
/>
)}
{step === 3 && (
<WizardStep3 migrationMethod={migrationMethod} setMigrationMethod={setMigrationMethod} />
)}
{step === 4 && (
<WizardStep4
migrationMethod={migrationMethod}
setMigrationMethod={setMigrationMethod}
inboundEmail={inboundEmail}
testEmailSent={testEmailSent}
setTestEmailSent={setTestEmailSent}
rssUrls={rssUrls}
setRssUrls={setRssUrls}
alertDescription={alertDescription}
setAlertDescription={setAlertDescription}
/>
)}
{step === 5 && (
<WizardStep5
selectedTemplate={selectedTemplate}
selectedRegions={selectedRegions}
setSelectedRegions={setSelectedRegions}
selectedPackages={selectedPackages}
setSelectedPackages={setSelectedPackages}
companyName={companyName}
migrationMethod={migrationMethod}
/>
)}
</div>
{/* Navigation Buttons */}
<div className="flex items-center gap-4 mt-8">
{step > 1 && (
<button
onClick={handleBack}
className={`px-6 py-3 rounded-xl font-medium transition-all ${
isDark
? 'bg-white/10 text-white hover:bg-white/20'
: 'bg-slate-200 text-slate-700 hover:bg-slate-300'
}`}
>
Zurueck
</button>
)}
<button
onClick={handleNext}
disabled={!canProceed()}
className={`px-8 py-3 rounded-xl font-medium transition-all ${
canProceed()
? 'bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:shadow-xl hover:shadow-purple-500/30 hover:scale-105'
: isDark
? 'bg-white/10 text-white/30 cursor-not-allowed'
: 'bg-slate-200 text-slate-400 cursor-not-allowed'
}`}
>
{step === totalSteps ? 'Einrichtung abschliessen →' : 'Weiter →'}
</button>
</div>
{/* Skip Option */}
{onSkip && step === 1 && (
<button
onClick={onSkip}
className={`mt-4 text-sm ${isDark ? 'text-white/40 hover:text-white/60' : 'text-slate-400 hover:text-slate-600'}`}
>
Ueberspringen (spaeter einrichten)
</button>
)}
</div>
</div>
)
}