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>
102 lines
6.3 KiB
TypeScript
102 lines
6.3 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { useTheme } from '@/lib/ThemeContext'
|
|
import { useAlerts, lehrerThemen, AlertImportance } from '@/lib/AlertsContext'
|
|
import { Step1TopicSelection, Step2Instructions, Step3Forwarding, Step4Settings } from './alerts-wizard/AlertsWizardSteps'
|
|
|
|
interface AlertsWizardProps {
|
|
onComplete: () => void
|
|
onSkip?: () => void
|
|
}
|
|
|
|
export function AlertsWizard({ onComplete, onSkip }: AlertsWizardProps) {
|
|
const { isDark } = useTheme()
|
|
const { addTopic, updateSettings } = useAlerts()
|
|
|
|
const [step, setStep] = useState(1)
|
|
const [selectedTopics, setSelectedTopics] = useState<string[]>([])
|
|
const [customTopic, setCustomTopic] = useState({ name: '', keywords: '' })
|
|
const [rssFeedUrl, setRssFeedUrl] = useState('')
|
|
const [notificationFrequency, setNotificationFrequency] = useState<'realtime' | 'hourly' | 'daily'>('daily')
|
|
const [minImportance, setMinImportance] = useState<AlertImportance>('PRUEFEN')
|
|
|
|
const totalSteps = 4
|
|
|
|
const handleNext = () => { if (step < totalSteps) setStep(step + 1); else completeWizard() }
|
|
const handleBack = () => { if (step > 1) setStep(step - 1) }
|
|
|
|
const completeWizard = () => {
|
|
selectedTopics.forEach(topicId => {
|
|
const topic = lehrerThemen.find(t => t.name === topicId)
|
|
if (topic) {
|
|
addTopic({ id: `topic-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, name: topic.name, keywords: topic.keywords, icon: topic.icon, isActive: true, rssFeedUrl: rssFeedUrl || undefined })
|
|
}
|
|
})
|
|
if (customTopic.name.trim()) {
|
|
addTopic({ id: `topic-${Date.now()}-custom`, name: customTopic.name, keywords: customTopic.keywords.split(',').map(k => k.trim()).filter(k => k), icon: '📌', isActive: true, rssFeedUrl: rssFeedUrl || undefined })
|
|
}
|
|
updateSettings({ notificationFrequency, minImportance, wizardCompleted: true })
|
|
onComplete()
|
|
}
|
|
|
|
const toggleTopic = (topicName: string) => {
|
|
setSelectedTopics(prev => prev.includes(topicName) ? prev.filter(t => t !== topicName) : [...prev, topicName])
|
|
}
|
|
|
|
const canProceed = () => {
|
|
if (step === 1) return selectedTopics.length > 0 || customTopic.name.trim().length > 0
|
|
return true
|
|
}
|
|
|
|
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'}`}>
|
|
<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-amber-500 opacity-50' : 'bg-amber-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-orange-500 opacity-50' : 'bg-orange-300 opacity-30'}`} style={{ animationDelay: '1s' }} />
|
|
</div>
|
|
|
|
<div className="relative z-10 flex-1 flex flex-col items-center justify-center p-8">
|
|
<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-amber-400 to-orange-500 flex items-center justify-center text-3xl shadow-lg">🔔</div>
|
|
<div className="text-left">
|
|
<h1 className={`text-2xl font-bold ${isDark ? 'text-white' : 'text-slate-900'}`}>Google Alerts einrichten</h1>
|
|
<p className={`text-sm ${isDark ? 'text-white/60' : 'text-slate-500'}`}>Bleiben Sie informiert ueber Bildungsthemen</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<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-amber-400 to-orange-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-amber-400 to-orange-500 transition-all duration-500" style={{ width: `${((step - 1) / (totalSteps - 1)) * 100}%` }} />
|
|
</div>
|
|
</div>
|
|
|
|
<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 && <Step1TopicSelection selectedTopics={selectedTopics} onToggleTopic={toggleTopic} customTopic={customTopic} onCustomTopicChange={setCustomTopic} isDark={isDark} />}
|
|
{step === 2 && <Step2Instructions selectedTopics={selectedTopics} isDark={isDark} />}
|
|
{step === 3 && <Step3Forwarding rssFeedUrl={rssFeedUrl} onRssFeedUrlChange={setRssFeedUrl} isDark={isDark} />}
|
|
{step === 4 && <Step4Settings notificationFrequency={notificationFrequency} onFrequencyChange={setNotificationFrequency} minImportance={minImportance} onMinImportanceChange={setMinImportance} selectedTopics={selectedTopics} customTopic={customTopic} rssFeedUrl={rssFeedUrl} isDark={isDark} />}
|
|
</div>
|
|
|
|
<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-amber-400 to-orange-500 text-white hover:shadow-xl hover:shadow-orange-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 ? 'Fertig! →' : 'Weiter →'}
|
|
</button>
|
|
</div>
|
|
|
|
{onSkip && (<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>
|
|
)
|
|
}
|