A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
849 lines
39 KiB
TypeScript
849 lines
39 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import { useTheme } from '@/lib/ThemeContext'
|
||
import { useAlertsB2B, B2BTemplate, Package, getPackageIcon, getPackageLabel } from '@/lib/AlertsB2BContext'
|
||
import { InfoBox, TipBox, StepBox } from './InfoBox'
|
||
|
||
interface B2BMigrationWizardProps {
|
||
onComplete: () => void
|
||
onSkip?: () => void
|
||
onCancel?: () => void
|
||
}
|
||
|
||
type MigrationMethod = 'email' | 'rss' | 'reconstruct' | null
|
||
|
||
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) {
|
||
// Special handling for step transitions
|
||
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 = () => {
|
||
// Save sources based on migration method
|
||
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
|
||
})
|
||
})
|
||
}
|
||
|
||
// Update settings
|
||
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 - Dashboard Style */}
|
||
<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 - Fixed Top Right */}
|
||
{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: Firmenname */}
|
||
{step === 1 && (
|
||
<div>
|
||
<h2 className={`text-2xl font-bold mb-2 text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
Willkommen im B2B-Bereich
|
||
</h2>
|
||
<p className={`mb-6 text-center ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
||
Wie heisst Ihr Unternehmen?
|
||
</p>
|
||
|
||
<div className="max-w-md mx-auto space-y-4">
|
||
<input
|
||
type="text"
|
||
placeholder="z.B. Hectronic GmbH"
|
||
value={companyName}
|
||
onChange={(e) => setCompanyName(e.target.value)}
|
||
className={`w-full px-4 py-3 rounded-xl border text-lg ${
|
||
isDark
|
||
? 'bg-white/10 border-white/20 text-white placeholder-white/40'
|
||
: 'bg-white border-slate-200 text-slate-900 placeholder-slate-400'
|
||
}`}
|
||
/>
|
||
|
||
<InfoBox variant="info" title="Warum fragen wir das?" icon="💡">
|
||
<p>Ihr Firmenname wird verwendet, um:</p>
|
||
<ul className="list-disc list-inside mt-2 space-y-1">
|
||
<li>Ihre eindeutige E-Mail-Adresse zu generieren</li>
|
||
<li>Berichte und Digests zu personalisieren</li>
|
||
<li>Ihr Dashboard anzupassen</li>
|
||
</ul>
|
||
</InfoBox>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Step 2: Template waehlen */}
|
||
{step === 2 && (
|
||
<div>
|
||
<h2 className={`text-2xl font-bold mb-2 text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
Branchenvorlage waehlen
|
||
</h2>
|
||
<p className={`mb-6 text-center ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
||
Waehlen Sie eine Vorlage fuer Ihre Branche oder starten Sie leer
|
||
</p>
|
||
|
||
<div className="space-y-4">
|
||
{availableTemplates.map((template) => (
|
||
<button
|
||
key={template.templateId}
|
||
onClick={() => setSelectedTemplateId(template.templateId)}
|
||
className={`w-full text-left p-5 rounded-xl border-2 transition-all ${
|
||
selectedTemplateId === template.templateId
|
||
? 'border-blue-500 bg-blue-500/20 shadow-lg'
|
||
: isDark
|
||
? 'border-white/10 bg-white/5 hover:bg-white/10'
|
||
: 'border-slate-200 bg-white hover:bg-slate-50'
|
||
}`}
|
||
>
|
||
<div className="flex items-start gap-4">
|
||
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-400 to-indigo-500 flex items-center justify-center text-2xl">
|
||
🏭
|
||
</div>
|
||
<div className="flex-1">
|
||
<h3 className={`font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
{template.templateName}
|
||
</h3>
|
||
<p className={`text-sm mt-1 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||
{template.templateDescription}
|
||
</p>
|
||
<div className="flex flex-wrap gap-2 mt-3">
|
||
{template.guidedConfig.packageSelector.options.map(pkg => (
|
||
<span
|
||
key={pkg}
|
||
className={`px-2 py-1 rounded-full text-xs font-medium ${
|
||
template.guidedConfig.packageSelector.default.includes(pkg)
|
||
? isDark ? 'bg-blue-500/30 text-blue-300' : 'bg-blue-100 text-blue-700'
|
||
: isDark ? 'bg-white/10 text-white/50' : 'bg-slate-100 text-slate-500'
|
||
}`}
|
||
>
|
||
{getPackageIcon(pkg)} {getPackageLabel(pkg)}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
{selectedTemplateId === template.templateId && (
|
||
<div className="w-6 h-6 rounded-full bg-blue-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>
|
||
))}
|
||
|
||
{/* Custom option */}
|
||
<button
|
||
onClick={() => setSelectedTemplateId('custom')}
|
||
className={`w-full text-left p-5 rounded-xl border-2 transition-all ${
|
||
selectedTemplateId === 'custom'
|
||
? 'border-blue-500 bg-blue-500/20 shadow-lg'
|
||
: isDark
|
||
? 'border-white/10 bg-white/5 hover:bg-white/10'
|
||
: 'border-slate-200 bg-white hover:bg-slate-50'
|
||
}`}
|
||
>
|
||
<div className="flex items-start gap-4">
|
||
<div className={`w-12 h-12 rounded-xl flex items-center justify-center text-2xl ${
|
||
isDark ? 'bg-white/20' : 'bg-slate-100'
|
||
}`}>
|
||
⚙️
|
||
</div>
|
||
<div className="flex-1">
|
||
<h3 className={`font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
Eigene Konfiguration
|
||
</h3>
|
||
<p className={`text-sm mt-1 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||
Starten Sie ohne Vorlage und konfigurieren Sie alles selbst
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Step 3: Migration Method */}
|
||
{step === 3 && (
|
||
<div>
|
||
<h2 className={`text-2xl font-bold mb-2 text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
Nutzen Sie bereits Google Alerts?
|
||
</h2>
|
||
<p className={`mb-6 text-center ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
||
Waehlen Sie, wie Sie Ihre bestehenden Alerts uebernehmen moechten
|
||
</p>
|
||
|
||
<div className="space-y-4">
|
||
{/* Email Forwarding (Recommended) */}
|
||
<button
|
||
onClick={() => setMigrationMethod('email')}
|
||
className={`w-full text-left p-5 rounded-xl border-2 transition-all ${
|
||
migrationMethod === 'email'
|
||
? 'border-green-500 bg-green-500/20 shadow-lg'
|
||
: isDark
|
||
? 'border-white/10 bg-white/5 hover:bg-white/10'
|
||
: 'border-slate-200 bg-white hover:bg-slate-50'
|
||
}`}
|
||
>
|
||
<div className="flex items-start gap-4">
|
||
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-green-400 to-emerald-500 flex items-center justify-center text-2xl">
|
||
📧
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-2">
|
||
<h3 className={`font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
E-Mail Weiterleitung
|
||
</h3>
|
||
<span className="px-2 py-0.5 rounded-full text-xs font-medium bg-green-500/20 text-green-500">
|
||
Empfohlen
|
||
</span>
|
||
</div>
|
||
<p className={`text-sm mt-1 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||
Leiten Sie Ihre bestehenden Google Alert E-Mails an uns weiter.
|
||
Keine Aenderung an Ihren Alerts noetig.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
|
||
{/* RSS Import */}
|
||
<button
|
||
onClick={() => setMigrationMethod('rss')}
|
||
className={`w-full text-left p-5 rounded-xl border-2 transition-all ${
|
||
migrationMethod === 'rss'
|
||
? 'border-blue-500 bg-blue-500/20 shadow-lg'
|
||
: isDark
|
||
? 'border-white/10 bg-white/5 hover:bg-white/10'
|
||
: 'border-slate-200 bg-white hover:bg-slate-50'
|
||
}`}
|
||
>
|
||
<div className="flex items-start gap-4">
|
||
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-400 to-indigo-500 flex items-center justify-center text-2xl">
|
||
📡
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-2">
|
||
<h3 className={`font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
RSS-Feed Import
|
||
</h3>
|
||
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${isDark ? 'bg-amber-500/20 text-amber-400' : 'bg-amber-100 text-amber-700'}`}>
|
||
Eingeschraenkt
|
||
</span>
|
||
</div>
|
||
<p className={`text-sm mt-1 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||
RSS-Feeds, falls in Ihrem Google-Konto verfuegbar.
|
||
</p>
|
||
<p className={`text-xs mt-1 ${isDark ? 'text-amber-400/70' : 'text-amber-600'}`}>
|
||
⚠️ Google hat RSS fuer viele Konten deaktiviert
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
|
||
{/* Reconstruction */}
|
||
<button
|
||
onClick={() => setMigrationMethod('reconstruct')}
|
||
className={`w-full text-left p-5 rounded-xl border-2 transition-all ${
|
||
migrationMethod === 'reconstruct'
|
||
? 'border-amber-500 bg-amber-500/20 shadow-lg'
|
||
: isDark
|
||
? 'border-white/10 bg-white/5 hover:bg-white/10'
|
||
: 'border-slate-200 bg-white hover:bg-slate-50'
|
||
}`}
|
||
>
|
||
<div className="flex items-start gap-4">
|
||
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center text-2xl">
|
||
🔄
|
||
</div>
|
||
<div className="flex-1">
|
||
<h3 className={`font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
Rekonstruktion
|
||
</h3>
|
||
<p className={`text-sm mt-1 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||
Beschreiben Sie, was Sie beobachten moechten. Wir erstellen die
|
||
optimale Konfiguration fuer Sie.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
|
||
<TipBox title="Kein Neustart noetig" icon="💡" className="mt-6">
|
||
<p>
|
||
Ihre bestehenden Google Alerts bleiben bestehen. Wir sind eine zusaetzliche
|
||
Intelligenzschicht, die filtert, priorisiert und zusammenfasst.
|
||
</p>
|
||
</TipBox>
|
||
</div>
|
||
)}
|
||
|
||
{/* Step 4: Migration Details */}
|
||
{step === 4 && (
|
||
<div>
|
||
{/* Email Forwarding */}
|
||
{migrationMethod === 'email' && (
|
||
<>
|
||
<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'}`}>
|
||
Google Alerts sendet E-Mails - leiten Sie diese einfach an uns weiter
|
||
</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 einen Gmail-Filter ein, der diese E-Mails automatisch weiterleitet -
|
||
wir uebernehmen die Verarbeitung und Auswertung.
|
||
</p>
|
||
</InfoBox>
|
||
|
||
<div className="space-y-6">
|
||
{/* Inbound Email */}
|
||
<div className={`p-4 rounded-xl border-2 ${isDark ? 'bg-green-500/10 border-green-500/30' : 'bg-green-50 border-green-200'}`}>
|
||
<label className={`block text-sm font-medium mb-2 ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
|
||
Ihre eindeutige Weiterleitungsadresse:
|
||
</label>
|
||
<div className="flex gap-2">
|
||
<input
|
||
type="text"
|
||
readOnly
|
||
value={inboundEmail}
|
||
className={`flex-1 px-4 py-3 rounded-lg border font-mono text-sm ${
|
||
isDark
|
||
? 'bg-white/5 border-white/20 text-white'
|
||
: 'bg-white border-slate-200 text-slate-900'
|
||
}`}
|
||
/>
|
||
<button
|
||
onClick={() => navigator.clipboard.writeText(inboundEmail)}
|
||
className="px-4 py-2 rounded-lg bg-purple-500 text-white hover:bg-purple-600 transition-all"
|
||
>
|
||
Kopieren
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Steps */}
|
||
<div className="space-y-3">
|
||
<StepBox step={1} title="Gmail-Einstellungen oeffnen" isActive>
|
||
Oeffnen Sie <a href="https://mail.google.com/mail/u/0/#settings/filters" target="_blank" rel="noopener noreferrer" className="text-purple-400 hover:underline">Gmail → Einstellungen → Filter und blockierte Adressen</a>
|
||
</StepBox>
|
||
<StepBox step={2} title="Neuen Filter erstellen">
|
||
Klicken Sie auf "Neuen Filter erstellen" und geben Sie bei "Von" ein: <code className={`px-2 py-1 rounded ${isDark ? 'bg-white/10' : 'bg-slate-100'}`}>googlealerts-noreply@google.com</code>
|
||
</StepBox>
|
||
<StepBox step={3} title="Weiterleitung aktivieren">
|
||
Waehlen Sie "Weiterleiten an" und fuegen Sie die obige Adresse ein. Aktivieren Sie auch "Filter auf passende Konversationen anwenden".
|
||
</StepBox>
|
||
</div>
|
||
|
||
<TipBox title="Keine Aenderung an Ihren Google Alerts noetig" icon="✨" className="mt-4">
|
||
<p>
|
||
Ihre bestehenden Google Alerts bleiben unveraendert. Der Gmail-Filter leitet
|
||
eingehende Alert-E-Mails automatisch an uns weiter. Sie koennen die E-Mails
|
||
auch weiterhin in Ihrem Posteingang sehen.
|
||
</p>
|
||
</TipBox>
|
||
|
||
{/* Test Button */}
|
||
<div className={`p-4 rounded-xl border ${
|
||
testEmailSent
|
||
? isDark ? 'bg-green-500/10 border-green-500/30' : 'bg-green-50 border-green-200'
|
||
: isDark ? 'bg-white/5 border-white/10' : 'bg-white border-slate-200'
|
||
}`}>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className={`font-medium ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
{testEmailSent ? '✓ Test-E-Mail empfangen' : 'Verbindung testen'}
|
||
</p>
|
||
<p className={`text-sm ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||
{testEmailSent
|
||
? 'Die Weiterleitung funktioniert!'
|
||
: 'Senden Sie eine Test-E-Mail, um die Einrichtung zu pruefen'}
|
||
</p>
|
||
</div>
|
||
{!testEmailSent && (
|
||
<button
|
||
onClick={() => setTestEmailSent(true)}
|
||
className="px-4 py-2 rounded-lg bg-white/20 hover:bg-white/30 transition-all"
|
||
>
|
||
Test senden
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* RSS Import */}
|
||
{migrationMethod === 'rss' && (
|
||
<>
|
||
<h2 className={`text-2xl font-bold mb-2 text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
RSS-Feeds importieren
|
||
</h2>
|
||
<p className={`mb-6 text-center ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
||
Fuegen Sie die RSS-URLs Ihrer Google Alerts hinzu
|
||
</p>
|
||
|
||
{/* Warning Box */}
|
||
<InfoBox variant="warning" title="Wichtiger Hinweis zu RSS" icon="⚠️" className="mb-6">
|
||
<p>
|
||
Google hat die RSS-Option fuer viele Konten entfernt. Falls Sie in Google Alerts
|
||
kein RSS-Symbol sehen oder die Option "RSS-Feed" nicht verfuegbar ist,
|
||
nutzen Sie bitte stattdessen die <strong>E-Mail-Weiterleitung</strong>.
|
||
</p>
|
||
<button
|
||
onClick={() => setMigrationMethod('email')}
|
||
className="mt-3 text-sm font-medium text-purple-400 hover:text-purple-300 underline"
|
||
>
|
||
→ Zur E-Mail-Weiterleitung wechseln
|
||
</button>
|
||
</InfoBox>
|
||
|
||
<div className="space-y-4">
|
||
{rssUrls.map((url, idx) => (
|
||
<div key={idx} className="flex gap-2">
|
||
<input
|
||
type="url"
|
||
placeholder="https://www.google.de/alerts/feeds/..."
|
||
value={url}
|
||
onChange={(e) => {
|
||
const newUrls = [...rssUrls]
|
||
newUrls[idx] = e.target.value
|
||
setRssUrls(newUrls)
|
||
}}
|
||
className={`flex-1 px-4 py-3 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'
|
||
}`}
|
||
/>
|
||
{rssUrls.length > 1 && (
|
||
<button
|
||
onClick={() => setRssUrls(rssUrls.filter((_, i) => i !== idx))}
|
||
className={`p-3 rounded-lg ${isDark ? 'hover:bg-white/10' : 'hover:bg-slate-100'}`}
|
||
>
|
||
✕
|
||
</button>
|
||
)}
|
||
</div>
|
||
))}
|
||
|
||
<button
|
||
onClick={() => setRssUrls([...rssUrls, ''])}
|
||
className={`w-full py-3 rounded-lg border-2 border-dashed transition-all ${
|
||
isDark
|
||
? 'border-white/20 text-white/60 hover:border-white/40 hover:text-white'
|
||
: 'border-slate-200 text-slate-500 hover:border-slate-300 hover:text-slate-700'
|
||
}`}
|
||
>
|
||
+ Weiteren Feed hinzufuegen
|
||
</button>
|
||
|
||
<div className={`p-4 rounded-xl ${isDark ? 'bg-white/5' : 'bg-slate-50'}`}>
|
||
<p className={`text-sm font-medium mb-2 ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
|
||
Falls RSS verfuegbar ist:
|
||
</p>
|
||
<ol className={`text-sm space-y-1 ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||
<li>1. Oeffnen Sie google.de/alerts</li>
|
||
<li>2. Suchen Sie nach einem orangefarbenen RSS-Symbol</li>
|
||
<li>3. Klicken Sie darauf und kopieren Sie die URL</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* Reconstruction */}
|
||
{migrationMethod === 'reconstruct' && (
|
||
<>
|
||
<h2 className={`text-2xl font-bold mb-2 text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
Was moechten Sie beobachten?
|
||
</h2>
|
||
<p className={`mb-6 text-center ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
||
Beschreiben Sie Ihre Beobachtungsziele - wir erstellen die optimale Konfiguration
|
||
</p>
|
||
|
||
<div className="space-y-4">
|
||
<textarea
|
||
placeholder="z.B. Wir beobachten europaweite kommunale Ausschreibungen für Parkscheinautomaten, EV-Ladesäulen mit Bezahlterminals und Tankautomaten. Wir bekommen aktuell zu viele irrelevante Treffer wie News, Stellenanzeigen und Zubehör..."
|
||
value={alertDescription}
|
||
onChange={(e) => setAlertDescription(e.target.value)}
|
||
rows={6}
|
||
className={`w-full px-4 py-3 rounded-xl border resize-none ${
|
||
isDark
|
||
? 'bg-white/10 border-white/20 text-white placeholder-white/40'
|
||
: 'bg-white border-slate-200 text-slate-900 placeholder-slate-400'
|
||
}`}
|
||
/>
|
||
|
||
<InfoBox variant="tip" title="Je mehr Details, desto besser" icon="✨">
|
||
<p>Beschreiben Sie:</p>
|
||
<ul className="list-disc list-inside mt-2 space-y-1">
|
||
<li>Welche Produkte/Services Sie anbieten</li>
|
||
<li>Welche Kaeufer/Maerkte relevant sind</li>
|
||
<li>Was Sie aktuell stoert (zu viel News, Jobs, etc.)</li>
|
||
</ul>
|
||
</InfoBox>
|
||
|
||
{alertDescription.length > 50 && (
|
||
<div className={`p-4 rounded-xl ${isDark ? 'bg-green-500/10 border border-green-500/30' : 'bg-green-50 border border-green-200'}`}>
|
||
<div className="flex items-center gap-3">
|
||
<span className="text-2xl">🤖</span>
|
||
<div>
|
||
<p className={`font-medium ${isDark ? 'text-green-300' : 'text-green-700'}`}>
|
||
KI-Analyse bereit
|
||
</p>
|
||
<p className={`text-sm ${isDark ? 'text-green-300/70' : 'text-green-600'}`}>
|
||
Wir werden Ihre Beschreibung analysieren und optimale Filter erstellen.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* Step 5: Notification Settings */}
|
||
{step === 5 && (
|
||
<div>
|
||
<h2 className={`text-2xl font-bold mb-2 text-center ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
Benachrichtigungen konfigurieren
|
||
</h2>
|
||
<p className={`mb-6 text-center ${isDark ? 'text-white/60' : 'text-slate-600'}`}>
|
||
Wie moechten Sie ueber relevante Ausschreibungen informiert werden?
|
||
</p>
|
||
|
||
<div className="space-y-6">
|
||
{/* Regions */}
|
||
{selectedTemplate && (
|
||
<div>
|
||
<label className={`block text-sm font-medium mb-3 ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
|
||
Regionen
|
||
</label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{selectedTemplate.guidedConfig.regionSelector.options.map(region => (
|
||
<button
|
||
key={region}
|
||
onClick={() => {
|
||
if (selectedRegions.includes(region)) {
|
||
setSelectedRegions(selectedRegions.filter(r => r !== region))
|
||
} else {
|
||
setSelectedRegions([...selectedRegions, region])
|
||
}
|
||
}}
|
||
className={`px-3 py-2 rounded-lg text-sm font-medium transition-all ${
|
||
selectedRegions.includes(region)
|
||
? 'bg-blue-500 text-white'
|
||
: isDark
|
||
? 'bg-white/10 text-white/60 hover:bg-white/20'
|
||
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
||
}`}
|
||
>
|
||
{region}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Packages */}
|
||
{selectedTemplate && (
|
||
<div>
|
||
<label className={`block text-sm font-medium mb-3 ${isDark ? 'text-white/80' : 'text-slate-700'}`}>
|
||
Produktbereiche
|
||
</label>
|
||
<div className="flex flex-wrap gap-2">
|
||
{selectedTemplate.guidedConfig.packageSelector.options.map(pkg => (
|
||
<button
|
||
key={pkg}
|
||
onClick={() => {
|
||
if (selectedPackages.includes(pkg)) {
|
||
setSelectedPackages(selectedPackages.filter(p => p !== pkg))
|
||
} else {
|
||
setSelectedPackages([...selectedPackages, pkg])
|
||
}
|
||
}}
|
||
className={`px-3 py-2 rounded-lg text-sm font-medium transition-all ${
|
||
selectedPackages.includes(pkg)
|
||
? 'bg-blue-500 text-white'
|
||
: isDark
|
||
? 'bg-white/10 text-white/60 hover:bg-white/20'
|
||
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
|
||
}`}
|
||
>
|
||
{getPackageIcon(pkg)} {getPackageLabel(pkg)}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Summary */}
|
||
<div className={`p-4 rounded-xl ${isDark ? 'bg-white/5' : 'bg-slate-50'}`}>
|
||
<h4 className={`font-medium mb-3 ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||
Ihre Konfiguration
|
||
</h4>
|
||
<ul className={`space-y-2 text-sm ${isDark ? 'text-white/60' : 'text-slate-500'}`}>
|
||
<li>• Firma: <strong>{companyName}</strong></li>
|
||
<li>• Template: <strong>{selectedTemplate?.templateName || 'Eigene Konfiguration'}</strong></li>
|
||
<li>• Migration: <strong>{
|
||
migrationMethod === 'email' ? 'E-Mail Weiterleitung' :
|
||
migrationMethod === 'rss' ? 'RSS Import' : 'Rekonstruktion'
|
||
}</strong></li>
|
||
<li>• Regionen: <strong>{selectedRegions.join(', ')}</strong></li>
|
||
<li>• Produkte: <strong>{selectedPackages.map(p => getPackageLabel(p as Package)).join(', ')}</strong></li>
|
||
<li>• Digest: <strong>Taeglich um 08:00, max. 10 Treffer</strong></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<TipBox title="Bereit fuer den Start" icon="🚀">
|
||
<p>
|
||
Nach Abschluss werden wir Ihre Alerts analysieren und nur die wirklich
|
||
relevanten Ausschreibungen herausfiltern. Erwarten Sie ca. 80-90% weniger
|
||
irrelevante Treffer.
|
||
</p>
|
||
</TipBox>
|
||
</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-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>
|
||
)
|
||
}
|