Files
breakpilot-lehrer/studio-v2/components/AlertsWizard.tsx
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

553 lines
24 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState } from 'react'
import { useTheme } from '@/lib/ThemeContext'
import { useAlerts, lehrerThemen, Topic, AlertImportance } from '@/lib/AlertsContext'
import { InfoBox, TipBox, StepBox } from './InfoBox'
import { BPIcon } from './Logo'
interface AlertsWizardProps {
onComplete: () => void
onSkip?: () => void
}
export function AlertsWizard({ onComplete, onSkip }: AlertsWizardProps) {
const { isDark } = useTheme()
const { addTopic, updateSettings, settings } = 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 {
// Wizard abschliessen
completeWizard()
}
}
const handleBack = () => {
if (step > 1) {
setStep(step - 1)
}
}
const completeWizard = () => {
// Ausgewaehlte vordefinierte Topics hinzufuegen
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
})
}
})
// Custom Topic hinzufuegen falls vorhanden
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
})
}
// Settings speichern
updateSettings({
notificationFrequency,
minImportance,
wizardCompleted: true
})
onComplete()
}
const toggleTopic = (topicName: string) => {
setSelectedTopics(prev =>
prev.includes(topicName)
? prev.filter(t => t !== topicName)
: [...prev, topicName]
)
}
const canProceed = () => {
switch (step) {
case 1:
return selectedTopics.length > 0 || customTopic.name.trim().length > 0
case 2:
return true // Info-Schritt, immer weiter
case 3:
return true // RSS optional
case 4:
return true // Einstellungen immer gueltig
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-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">
{/* Logo & Titel */}
<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>
{/* 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-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>
{/* 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: Themen waehlen */}
{step === 1 && (
<div>
<h2 className={`text-2xl font-bold mb-2 text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
Welche Themen interessieren Sie?
</h2>
<p className={`mb-6 text-center ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
Waehlen Sie Themen, ueber die Sie informiert werden moechten
</p>
{/* Vordefinierte Themen */}
<div className="grid grid-cols-2 md:grid-cols-3 gap-3 mb-6">
{lehrerThemen.map((topic) => {
const isSelected = selectedTopics.includes(topic.name)
return (
<button
key={topic.name}
onClick={() => toggleTopic(topic.name)}
className={`p-4 rounded-xl border-2 transition-all hover:scale-105 text-left ${
isSelected
? 'border-amber-500 bg-amber-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-3">
<span className="text-2xl">{topic.icon}</span>
<div className="flex-1 min-w-0">
<p className={`font-medium truncate ${
isSelected
? isDark ? 'text-amber-300' : 'text-amber-700'
: isDark ? 'text-white' : 'text-slate-900'
}`}>
{topic.name}
</p>
<p className={`text-xs truncate ${isDark ? 'text-white/40' : 'text-slate-400'}`}>
{topic.keywords.slice(0, 2).join(', ')}
</p>
</div>
{isSelected && (
<div className="w-6 h-6 rounded-full bg-amber-500 flex items-center justify-center">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
)}
</div>
</button>
)
})}
</div>
{/* Custom Topic */}
<div className={`p-4 rounded-xl border ${isDark ? 'bg-white/5 border-white/10' : 'bg-slate-50 border-slate-200'}`}>
<h4 className={`font-medium mb-3 flex items-center gap-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
<span>📌</span> Eigenes Thema hinzufuegen
</h4>
<div className="space-y-3">
<input
type="text"
placeholder="Themenname (z.B. 'Mathematik Didaktik')"
value={customTopic.name}
onChange={(e) => setCustomTopic({ ...customTopic, name: e.target.value })}
className={`w-full px-4 py-2 rounded-lg border ${
isDark
? 'bg-white/10 border-white/20 text-white placeholder-white/40'
: 'bg-white border-slate-200 text-slate-900 placeholder-slate-400'
}`}
/>
<input
type="text"
placeholder="Stichwoerter (kommagetrennt)"
value={customTopic.keywords}
onChange={(e) => setCustomTopic({ ...customTopic, keywords: e.target.value })}
className={`w-full px-4 py-2 rounded-lg border ${
isDark
? 'bg-white/10 border-white/20 text-white placeholder-white/40'
: 'bg-white border-slate-200 text-slate-900 placeholder-slate-400'
}`}
/>
</div>
</div>
</div>
)}
{/* Step 2: Google Alerts Anleitung */}
{step === 2 && (
<div>
<h2 className={`text-2xl font-bold mb-2 text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
Google Alerts einrichten
</h2>
<p className={`mb-6 text-center ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
Google sendet Alerts per E-Mail - wir verarbeiten sie fuer Sie
</p>
<InfoBox variant="info" title="So funktioniert es" icon="💡" className="mb-6">
<p>
Google Alerts versendet Benachrichtigungen per E-Mail an Ihr Konto.
Sie richten einfach eine Weiterleitung ein - wir uebernehmen die
Auswertung, Filterung und Zusammenfassung.
</p>
</InfoBox>
<div className="space-y-4">
<StepBox step={1} title="Google Alerts oeffnen" isActive>
<p className="mb-2">
Besuchen Sie <a
href="https://www.google.de/alerts"
target="_blank"
rel="noopener noreferrer"
className="text-amber-500 hover:underline font-medium"
>
google.de/alerts
</a> und melden Sie sich mit Ihrem Google-Konto an.
</p>
</StepBox>
<StepBox step={2} title="Alerts erstellen">
<p>
Geben Sie Suchbegriffe ein (z.B. &quot;{selectedTopics[0] || 'Bildungspolitik'}&quot;)
und erstellen Sie Alerts. Die Alerts werden an Ihre E-Mail-Adresse gesendet.
</p>
</StepBox>
<StepBox step={3} title="E-Mail-Weiterleitung einrichten">
<p>
Im naechsten Schritt richten Sie eine automatische Weiterleitung
der Google Alert E-Mails an uns ein. So verarbeiten wir Ihre Alerts
automatisch.
</p>
</StepBox>
</div>
<TipBox title="Tipp: Mehrere Alerts kombinieren" icon="💡" className="mt-6">
<p>
Sie koennen beliebig viele Google Alerts erstellen. Alle werden
per E-Mail an Sie gesendet und durch die Weiterleitung automatisch
verarbeitet - gefiltert, priorisiert und zusammengefasst.
</p>
</TipBox>
</div>
)}
{/* Step 3: E-Mail Weiterleitung einrichten */}
{step === 3 && (
<div>
<h2 className={`text-2xl font-bold mb-2 text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
E-Mail Weiterleitung einrichten
</h2>
<p className={`mb-6 text-center ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
Leiten Sie Ihre Google Alert E-Mails automatisch an uns weiter
</p>
<div className="space-y-4">
{/* Empfohlene Methode: E-Mail Weiterleitung */}
<div className={`p-5 rounded-xl border-2 ${isDark ? 'border-green-500/50 bg-green-500/10' : 'border-green-500 bg-green-50'}`}>
<div className="flex items-start gap-3 mb-4">
<span className="text-2xl">📧</span>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h4 className={`font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
E-Mail Weiterleitung
</h4>
<span className="px-2 py-0.5 rounded-full text-xs font-medium bg-green-500/20 text-green-600">
Empfohlen
</span>
</div>
<p className={`text-sm ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
Richten Sie in Gmail einen Filter ein, der Google Alert E-Mails automatisch weiterleitet.
</p>
</div>
</div>
<div className={`p-3 rounded-lg ${isDark ? 'bg-white/10' : 'bg-white'}`}>
<p className={`text-sm font-medium mb-2 ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
Ihre Weiterleitungsadresse:
</p>
<div className="flex gap-2">
<code className={`flex-1 px-3 py-2 rounded-lg text-sm font-mono ${
isDark ? 'bg-white/10 text-amber-300' : 'bg-slate-100 text-amber-600'
}`}>
alerts@breakpilot.de
</code>
<button
onClick={() => navigator.clipboard.writeText('alerts@breakpilot.de')}
className="px-3 py-2 rounded-lg bg-amber-500 text-white text-sm hover:bg-amber-600 transition-all"
>
Kopieren
</button>
</div>
</div>
<div className="mt-4 space-y-2">
<p className={`text-sm font-medium ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
So richten Sie die Weiterleitung in Gmail ein:
</p>
<ol className={`text-sm space-y-1 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
<li>1. Oeffnen Sie Gmail Einstellungen Filter</li>
<li>2. Neuer Filter: Von &quot;googlealerts-noreply@google.com&quot;</li>
<li>3. Aktion: Weiterleiten an &quot;alerts@breakpilot.de&quot;</li>
</ol>
</div>
</div>
{/* Alternative: RSS (mit Warnung) */}
<div className={`p-4 rounded-xl border ${isDark ? 'border-white/10 bg-white/5' : 'border-slate-200 bg-slate-50'}`}>
<div className="flex items-start gap-3">
<span className="text-xl">📡</span>
<div className="flex-1">
<h4 className={`font-medium mb-1 ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
Alternativ: RSS-Feed (eingeschraenkt verfuegbar)
</h4>
<p className={`text-sm mb-3 ${isDark ? 'text-white/50' : 'text-slate-500'}`}>
Google hat die RSS-Option fuer viele Konten entfernt. Falls Sie RSS noch sehen,
koennen Sie die Feed-URL hier eingeben:
</p>
<input
type="url"
placeholder="https://www.google.de/alerts/feeds/... (falls verfuegbar)"
value={rssFeedUrl}
onChange={(e) => setRssFeedUrl(e.target.value)}
className={`w-full px-4 py-2 rounded-lg border text-sm ${
isDark
? 'bg-white/10 border-white/20 text-white placeholder-white/30'
: 'bg-white border-slate-200 text-slate-900 placeholder-slate-400'
}`}
/>
<p className={`text-xs mt-2 ${isDark ? 'text-amber-400/70' : 'text-amber-600'}`}>
Die meisten Nutzer sehen keine RSS-Option mehr in Google Alerts.
Verwenden Sie in diesem Fall die E-Mail-Weiterleitung.
</p>
</div>
</div>
</div>
<div className={`p-4 rounded-xl ${isDark ? 'bg-white/5' : 'bg-slate-50'}`}>
<p className={`text-sm ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
Sie koennen diesen Schritt auch ueberspringen und die Weiterleitung spaeter einrichten.
Die Demo-Alerts werden weiterhin angezeigt.
</p>
</div>
</div>
</div>
)}
{/* Step 4: Benachrichtigungs-Einstellungen */}
{step === 4 && (
<div>
<h2 className={`text-2xl font-bold mb-2 text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
Benachrichtigungen einstellen
</h2>
<p className={`mb-6 text-center ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
Wie moechten Sie informiert werden?
</p>
<div className="space-y-6">
{/* Frequenz */}
<div>
<label className={`block text-sm font-medium mb-3 ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
Wie oft moechten Sie Alerts erhalten?
</label>
<div className="grid grid-cols-3 gap-3">
{[
{ id: 'realtime', label: 'Sofort', icon: '⚡', desc: 'Bei jedem neuen Alert' },
{ id: 'hourly', label: 'Stuendlich', icon: '🕐', desc: 'Zusammenfassung pro Stunde' },
{ id: 'daily', label: 'Taeglich', icon: '📅', desc: 'Einmal am Tag' },
].map((freq) => (
<button
key={freq.id}
onClick={() => setNotificationFrequency(freq.id as any)}
className={`p-4 rounded-xl border-2 transition-all text-center ${
notificationFrequency === freq.id
? 'border-amber-500 bg-amber-500/20'
: isDark
? 'border-white/10 bg-white/5 hover:bg-white/10'
: 'border-slate-200 bg-white hover:bg-slate-50'
}`}
>
<span className="text-2xl block mb-1">{freq.icon}</span>
<p className={`font-medium ${isDark ? 'text-white' : 'text-slate-900'}`}>{freq.label}</p>
<p className={`text-xs ${isDark ? 'text-white/40' : 'text-slate-400'}`}>{freq.desc}</p>
</button>
))}
</div>
</div>
{/* Mindest-Wichtigkeit */}
<div>
<label className={`block text-sm font-medium mb-3 ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
Mindest-Wichtigkeit fuer Benachrichtigungen
</label>
<div className="grid grid-cols-5 gap-2">
{[
{ id: 'KRITISCH', label: 'Kritisch', color: 'red' },
{ id: 'DRINGEND', label: 'Dringend', color: 'orange' },
{ id: 'WICHTIG', label: 'Wichtig', color: 'yellow' },
{ id: 'PRUEFEN', label: 'Pruefen', color: 'blue' },
{ id: 'INFO', label: 'Info', color: 'slate' },
].map((imp) => (
<button
key={imp.id}
onClick={() => setMinImportance(imp.id as AlertImportance)}
className={`p-2 rounded-lg border-2 transition-all text-center text-xs ${
minImportance === imp.id
? `border-${imp.color}-500 bg-${imp.color}-500/20`
: isDark
? 'border-white/10 bg-white/5 hover:bg-white/10'
: 'border-slate-200 bg-white hover:bg-slate-50'
}`}
>
<p className={`font-medium ${isDark ? 'text-white' : 'text-slate-900'}`}>{imp.label}</p>
</button>
))}
</div>
<p className={`text-xs mt-2 ${isDark ? 'text-white/40' : 'text-slate-400'}`}>
Sie erhalten nur Benachrichtigungen fuer Alerts mit dieser Wichtigkeit oder hoeher.
</p>
</div>
{/* Zusammenfassung */}
<div className={`p-4 rounded-xl ${isDark ? 'bg-white/5' : 'bg-slate-50'}`}>
<h4 className={`font-medium mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
Ihre Einstellungen
</h4>
<ul className={`text-sm space-y-1 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
<li> {selectedTopics.length + (customTopic.name ? 1 : 0)} Themen ausgewaehlt</li>
<li> Benachrichtigungen: {notificationFrequency === 'realtime' ? 'Sofort' : notificationFrequency === 'hourly' ? 'Stuendlich' : 'Taeglich'}</li>
<li> Mindest-Wichtigkeit: {minImportance}</li>
{rssFeedUrl && <li> RSS-Feed verbunden</li>}
</ul>
</div>
</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'
}`}
>
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>
{/* Skip Option */}
{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>
)
}