website (17 pages + 3 components): - multiplayer/wizard, middleware/wizard+test-wizard, communication - builds/wizard, staff-search, voice, sbom/wizard - foerderantrag, mail/tasks, tools/communication, sbom - compliance/evidence, uni-crawler, brandbook (already done) - CollectionsTab, IngestionTab, RiskHeatmap backend-lehrer (5 files): - letters_api (641 → 2), certificates_api (636 → 2) - alerts_agent/db/models (636 → 3) - llm_gateway/communication_service (614 → 2) - game/database already done in prior batch klausur-service (2 files): - hybrid_vocab_extractor (664 → 2) - klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2) voice-service (3 files): - bqas/rag_judge (618 → 3), runner (529 → 2) - enhanced_task_orchestrator (519 → 2) studio-v2 (6 files): - korrektur/[klausurId] (578 → 4), fairness (569 → 2) - AlertsWizard (552 → 2), OnboardingWizard (513 → 2) - korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
348 lines
14 KiB
TypeScript
348 lines
14 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { useLanguage } from '@/lib/LanguageContext'
|
|
import { useTheme } from '@/lib/ThemeContext'
|
|
import { GermanyMap, bundeslaender } from './GermanyMap'
|
|
import { CityMap } from './CityMap'
|
|
import { SchoolSearch } from './SchoolSearch'
|
|
import { BPIcon } from './Logo'
|
|
|
|
interface OnboardingWizardProps {
|
|
onComplete: (data: OnboardingData) => void
|
|
}
|
|
|
|
export interface OnboardingData {
|
|
bundesland: string
|
|
bundeslandName: string
|
|
city: string
|
|
cityLat?: number
|
|
cityLng?: number
|
|
schoolName: string
|
|
schoolType: string
|
|
}
|
|
|
|
import { schulformen, schulformKategorien } from './onboarding-wizard/schulformen'
|
|
|
|
export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
|
|
const { t } = useLanguage()
|
|
const { isDark } = useTheme()
|
|
|
|
const [step, setStep] = useState(1)
|
|
const [data, setData] = useState<Partial<OnboardingData>>({})
|
|
const [citySearch, setCitySearch] = useState('')
|
|
const [schoolSearch, setSchoolSearch] = useState('')
|
|
|
|
const totalSteps = 4
|
|
|
|
const handleNext = () => {
|
|
if (step < totalSteps) {
|
|
setStep(step + 1)
|
|
} else {
|
|
// Abschluss
|
|
onComplete(data as OnboardingData)
|
|
}
|
|
}
|
|
|
|
const handleBack = () => {
|
|
if (step > 1) {
|
|
setStep(step - 1)
|
|
}
|
|
}
|
|
|
|
const canProceed = () => {
|
|
switch (step) {
|
|
case 1:
|
|
return !!data.bundesland
|
|
case 2:
|
|
return !!data.city && data.city.trim().length > 0
|
|
case 3:
|
|
return !!data.schoolName && data.schoolName.trim().length > 0
|
|
case 4:
|
|
return !!data.schoolType
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
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 */}
|
|
<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-pulse ${
|
|
isDark ? 'bg-purple-500 opacity-50' : 'bg-purple-300 opacity-30'
|
|
}`} />
|
|
<div className={`absolute -bottom-40 -left-40 w-80 h-80 rounded-full mix-blend-multiply filter blur-3xl animate-pulse ${
|
|
isDark ? 'bg-blue-500 opacity-50' : 'bg-blue-300 opacity-30'
|
|
}`} style={{ animationDelay: '1s' }} />
|
|
</div>
|
|
|
|
<div className="relative z-10 flex-1 flex flex-col items-center justify-center p-8">
|
|
{/* Logo & Willkommen */}
|
|
<div className="text-center mb-8">
|
|
<div className="flex items-center justify-center gap-3 mb-4">
|
|
<BPIcon variant="cupertino" size={56} />
|
|
<div className="text-left">
|
|
<h1 className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
BreakPilot Studio
|
|
</h1>
|
|
<p className={`text-sm ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
|
Willkommen! Lassen Sie uns loslegen.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress Bar */}
|
|
<div className="w-full max-w-2xl mb-8">
|
|
<div className="flex items-center justify-between mb-2">
|
|
{[1, 2, 3, 4].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-blue-500 to-purple-500 text-white scale-110 shadow-lg'
|
|
: 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-blue-500 to-purple-500 transition-all duration-500"
|
|
style={{ width: `${((step - 1) / (totalSteps - 1)) * 100}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Card */}
|
|
<div className={`w-full max-w-2xl 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: Bundesland */}
|
|
{step === 1 && (
|
|
<div className="text-center">
|
|
<h2 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
In welchem Bundesland unterrichten Sie?
|
|
</h2>
|
|
<p className={`mb-6 ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
|
Klicken Sie auf Ihr Bundesland in der Karte
|
|
</p>
|
|
<GermanyMap
|
|
selectedState={data.bundesland || null}
|
|
suggestedState="HH"
|
|
onSelectState={(stateId) => setData({
|
|
...data,
|
|
bundesland: stateId,
|
|
bundeslandName: bundeslaender[stateId as keyof typeof bundeslaender]
|
|
})}
|
|
className="mx-auto max-w-md"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Step 2: Stadt */}
|
|
{step === 2 && (
|
|
<div className="text-center">
|
|
<h2 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
In welcher Stadt arbeiten Sie?
|
|
</h2>
|
|
<p className={`mb-4 ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
|
Suchen Sie Ihre Stadt oder klicken Sie auf die Karte
|
|
</p>
|
|
|
|
{/* Info Box - Bundesland */}
|
|
<div className={`inline-flex items-center gap-2 px-4 py-2 rounded-full mb-4 ${
|
|
isDark ? 'bg-white/10' : 'bg-slate-100'
|
|
}`}>
|
|
<span className="text-lg">📍</span>
|
|
<span className={`text-sm font-medium ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
|
|
{data.bundeslandName}
|
|
</span>
|
|
</div>
|
|
|
|
{/* CityMap Komponente */}
|
|
<CityMap
|
|
bundesland={data.bundesland || 'HH'}
|
|
bundeslandName={data.bundeslandName || 'Hamburg'}
|
|
selectedCity={data.city || ''}
|
|
onSelectCity={(city, lat, lng) => setData({
|
|
...data,
|
|
city,
|
|
cityLat: lat,
|
|
cityLng: lng
|
|
})}
|
|
className="max-w-lg mx-auto"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Step 3: Schule */}
|
|
{step === 3 && (
|
|
<div className="text-center">
|
|
<h2 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
Wie heißt Ihre Schule?
|
|
</h2>
|
|
<p className={`mb-4 ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
|
Suchen Sie Ihre Schule oder geben Sie den Namen ein
|
|
</p>
|
|
|
|
{/* SchoolSearch Komponente mit Autocomplete */}
|
|
<SchoolSearch
|
|
city={data.city || ''}
|
|
bundesland={data.bundesland || 'HH'}
|
|
bundeslandName={data.bundeslandName || 'Hamburg'}
|
|
selectedSchool={data.schoolName || ''}
|
|
onSelectSchool={(schoolName, schoolId) => setData({
|
|
...data,
|
|
schoolName
|
|
})}
|
|
className="max-w-lg mx-auto"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Step 4: Schulform */}
|
|
{step === 4 && (
|
|
<div className="text-center">
|
|
<h2 className={`text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
|
Welche Schulform ist es?
|
|
</h2>
|
|
<p className={`mb-6 ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
|
Wählen Sie die passende Schulform
|
|
</p>
|
|
|
|
{/* Scrollbarer Bereich mit Kategorien */}
|
|
<div className="max-h-[400px] overflow-y-auto pr-2 space-y-6">
|
|
{schulformKategorien.map((kategorie) => {
|
|
const formenInKategorie = schulformen.filter(f => f.category === kategorie.id)
|
|
if (formenInKategorie.length === 0) return null
|
|
|
|
return (
|
|
<div key={kategorie.id}>
|
|
{/* Kategorie-Header */}
|
|
<div className={`flex items-center gap-2 mb-3 ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
|
|
<span>{kategorie.icon}</span>
|
|
<span className="text-sm font-medium">{kategorie.name}</span>
|
|
<div className={`flex-1 h-px ${isDark ? 'bg-white/10' : 'bg-slate-200'}`} />
|
|
</div>
|
|
|
|
{/* Schulformen in dieser Kategorie */}
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
{formenInKategorie.map((form) => {
|
|
const isSelected = data.schoolType === form.id
|
|
return (
|
|
<button
|
|
key={form.id}
|
|
onClick={() => setData({ ...data, schoolType: form.id })}
|
|
className={`p-3 rounded-xl border-2 transition-all hover:scale-105 text-left ${
|
|
isSelected
|
|
? 'border-blue-500 bg-blue-500/20 shadow-lg'
|
|
: isDark
|
|
? 'border-white/10 bg-white/5 hover:bg-white/10 hover:border-white/20'
|
|
: 'border-slate-200 bg-white hover:bg-slate-50 hover:border-slate-300'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xl">{form.icon}</span>
|
|
<div className="flex-1 min-w-0">
|
|
<p className={`font-medium text-sm truncate ${
|
|
isSelected
|
|
? isDark ? 'text-blue-300' : 'text-blue-700'
|
|
: isDark ? 'text-white' : 'text-slate-900'
|
|
}`}>
|
|
{form.name}
|
|
</p>
|
|
<p className={`text-xs truncate ${isDark ? 'text-white/40' : 'text-slate-400'}`}>
|
|
{form.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Summary */}
|
|
{data.schoolType && (
|
|
<div className={`mt-6 p-4 rounded-xl ${
|
|
isDark ? 'bg-white/5' : 'bg-slate-50'
|
|
}`}>
|
|
<p className={`text-sm ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
|
|
<strong>{data.schoolName}</strong>
|
|
</p>
|
|
<p className={`text-xs ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
|
|
{schulformen.find(f => f.id === data.schoolType)?.name} in {data.city}, {data.bundeslandName}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</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'
|
|
}`}
|
|
>
|
|
← Zurück
|
|
</button>
|
|
)}
|
|
|
|
<button
|
|
onClick={handleNext}
|
|
disabled={!canProceed()}
|
|
className={`px-8 py-3 rounded-xl font-medium transition-all ${
|
|
canProceed()
|
|
? 'bg-gradient-to-r from-blue-500 to-purple-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 ? 'Los geht\'s! →' : 'Weiter →'}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Skip Option */}
|
|
<button
|
|
onClick={() => onComplete({
|
|
bundesland: data.bundesland || 'NI',
|
|
bundeslandName: data.bundeslandName || 'Niedersachsen',
|
|
city: data.city || 'Unbekannt',
|
|
schoolName: data.schoolName || 'Meine Schule',
|
|
schoolType: data.schoolType || 'gymnasium'
|
|
})}
|
|
className={`mt-4 text-sm ${isDark ? 'text-white/40 hover:text-white/60' : 'text-slate-400 hover:text-slate-600'}`}
|
|
>
|
|
Überspringen (später einrichten)
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|