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>
309 lines
11 KiB
TypeScript
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>
|
|
)
|
|
}
|