680 lines
29 KiB
TypeScript
680 lines
29 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
import { useRouter } from 'next/navigation'
|
||
import { motion, AnimatePresence } from 'framer-motion'
|
||
import { Sparkles, ShieldCheck, Mail, ArrowRight, Loader2, Check, Lock, Building, User, MapPin, Receipt, Palette, Send } from 'lucide-react'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Input } from '@/components/ui/input'
|
||
import { Label } from '@/components/ui/label'
|
||
import { createClient } from '@/lib/supabase/client'
|
||
import { completeSetup, testSmtpConfig } from '@/lib/actions/setup'
|
||
import { ColorThemePicker } from '@/components/admin/ColorThemePicker'
|
||
|
||
export function SetupWizard() {
|
||
const router = useRouter()
|
||
const [step, setStep] = useState(1)
|
||
const [loading, setLoading] = useState(false)
|
||
const [errorMsg, setErrorMsg] = useState('')
|
||
const [testSmtpLoading, setTestSmtpLoading] = useState(false)
|
||
const [testSmtpResult, setTestSmtpResult] = useState<{ success: boolean; message: string } | null>(null)
|
||
|
||
// Step 2: Admin Account
|
||
const [adminForm, setAdminForm] = useState({
|
||
email: '',
|
||
password: '',
|
||
confirmPassword: '',
|
||
firstName: '',
|
||
lastName: '',
|
||
})
|
||
|
||
// Step 3: Firmendaten & Rechnungsadresse
|
||
const [brandingForm, setBrandingForm] = useState({
|
||
companyName: '',
|
||
street: '',
|
||
zip: '',
|
||
city: '',
|
||
billingStreet: '',
|
||
billingZip: '',
|
||
billingCity: '',
|
||
sameBillingAddress: true,
|
||
colorScheme: 'modern_blue',
|
||
primaryColor: '#2563eb',
|
||
accentColor: '#38bdf8',
|
||
})
|
||
|
||
// Step 5: SMTP Config
|
||
const [smtpForm, setSmtpForm] = useState({
|
||
host: '',
|
||
port: '587',
|
||
secure: false,
|
||
user: '',
|
||
pass: '',
|
||
})
|
||
|
||
// Validations
|
||
const isAdminFormValid =
|
||
adminForm.email.includes('@') &&
|
||
adminForm.password.length >= 6 &&
|
||
adminForm.password === adminForm.confirmPassword &&
|
||
adminForm.firstName.trim().length > 0 &&
|
||
adminForm.lastName.trim().length > 0
|
||
|
||
const isCompanyFormValid =
|
||
brandingForm.companyName.trim().length > 0 &&
|
||
brandingForm.street.trim().length > 0 &&
|
||
brandingForm.zip.trim().length > 0 &&
|
||
brandingForm.city.trim().length > 0 &&
|
||
(brandingForm.sameBillingAddress ||
|
||
(brandingForm.billingStreet.trim().length > 0 &&
|
||
brandingForm.billingZip.trim().length > 0 &&
|
||
brandingForm.billingCity.trim().length > 0))
|
||
|
||
const handleTestSmtp = async () => {
|
||
setTestSmtpLoading(true)
|
||
setTestSmtpResult(null)
|
||
const res = await testSmtpConfig(
|
||
{
|
||
host: smtpForm.host,
|
||
port: Number(smtpForm.port),
|
||
secure: smtpForm.secure,
|
||
user: smtpForm.user,
|
||
pass: smtpForm.pass,
|
||
},
|
||
adminForm.email
|
||
)
|
||
setTestSmtpResult(res)
|
||
setTestSmtpLoading(false)
|
||
}
|
||
|
||
const handleFinishSetup = async () => {
|
||
if (!isAdminFormValid || !isCompanyFormValid) return
|
||
setLoading(true)
|
||
setErrorMsg('')
|
||
|
||
try {
|
||
const res = await completeSetup(
|
||
{
|
||
email: adminForm.email,
|
||
password: adminForm.password,
|
||
companyName: brandingForm.companyName,
|
||
firstName: adminForm.firstName,
|
||
lastName: adminForm.lastName,
|
||
},
|
||
{
|
||
street: brandingForm.street,
|
||
zip: brandingForm.zip,
|
||
city: brandingForm.city,
|
||
billingStreet: brandingForm.billingStreet,
|
||
billingZip: brandingForm.billingZip,
|
||
billingCity: brandingForm.billingCity,
|
||
sameBillingAddress: brandingForm.sameBillingAddress,
|
||
colorScheme: brandingForm.colorScheme,
|
||
primaryColor: brandingForm.primaryColor,
|
||
accentColor: brandingForm.accentColor,
|
||
},
|
||
{
|
||
host: smtpForm.host,
|
||
port: Number(smtpForm.port),
|
||
secure: smtpForm.secure,
|
||
user: smtpForm.user,
|
||
pass: smtpForm.pass,
|
||
}
|
||
)
|
||
|
||
if (!res.success) {
|
||
setErrorMsg(res.error || 'Fehler beim Setup.')
|
||
setLoading(false)
|
||
return
|
||
}
|
||
|
||
// Setup erfolgreich – Weiterleitung zur Login-Seite
|
||
router.push('/auth/login?setup=success')
|
||
router.refresh()
|
||
} catch (e: any) {
|
||
setErrorMsg(e.message || 'Ein unerwarteter Fehler ist aufgetreten.')
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-[#020617] text-white flex flex-col items-center justify-center p-4 relative overflow-hidden">
|
||
{/* Background Glow */}
|
||
<div className="absolute top-[-10%] left-[-10%] w-[50%] h-[50%] rounded-full bg-blue-500/10 blur-[120px]" />
|
||
<div className="absolute bottom-[-10%] right-[-10%] w-[50%] h-[50%] rounded-full bg-purple-500/10 blur-[120px]" />
|
||
|
||
<div className="w-full max-w-2xl relative z-10 my-8">
|
||
{/* Step Indicator Header */}
|
||
<div className="flex items-center justify-between mb-6 px-2">
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-xl font-bold tracking-tight text-blue-400">B2B Shop</span>
|
||
<span className="text-xs px-2.5 py-0.5 rounded-full bg-slate-900 text-slate-400 border border-slate-800 font-medium">made by hephex</span>
|
||
</div>
|
||
<div className="flex gap-1.5">
|
||
{[1, 2, 3, 4, 5].map((s) => (
|
||
<div
|
||
key={s}
|
||
className={`h-1.5 rounded-full transition-all duration-300 ${s === step ? 'w-8 bg-blue-500' : 'w-2 bg-slate-800'
|
||
}`}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<AnimatePresence mode="wait">
|
||
{/* STEP 1: Willkommen */}
|
||
{step === 1 && (
|
||
<motion.div
|
||
key="step1"
|
||
initial={{ opacity: 0, y: 15 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -15 }}
|
||
className="glass-dark border border-white/10 rounded-3xl p-8 space-y-6 shadow-2xl"
|
||
>
|
||
<div className="w-16 h-16 rounded-2xl bg-blue-500/10 border border-blue-500/20 flex items-center justify-center text-blue-400 mx-auto shadow-inner">
|
||
<Sparkles className="w-8 h-8 animate-pulse" />
|
||
</div>
|
||
|
||
<div className="text-center space-y-2">
|
||
<h1 className="text-3xl font-extrabold tracking-tight">B2B Shop made by hephex</h1>
|
||
<p className="text-slate-400 leading-relaxed text-sm">
|
||
Richten Sie Ihren persönlichen Lizenz- und Anfrage-Shop ein. Wir konfigurieren Administrator-Zugang, Firmendaten, Rechnungsadresse und Ihr persönliches Farbschema.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="p-4 rounded-xl bg-white/5 border border-white/5 text-sm text-slate-400 space-y-2">
|
||
<div className="flex items-center gap-2">
|
||
<Check className="w-4 h-4 text-green-400 shrink-0" />
|
||
<span>Komplett self-hosted & sicher</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Check className="w-4 h-4 text-green-400 shrink-0" />
|
||
<span>9 vordefinierte Farbpaletten + Custom Farbwähler</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Check className="w-4 h-4 text-green-400 shrink-0" />
|
||
<span>Lizenzserver-Anbindung</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Check className="w-4 h-4 text-green-400 shrink-0" />
|
||
<span>Stammdatenverwaltung</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<Check className="w-4 h-4 text-green-400 shrink-0" />
|
||
<span>Automatische Adress- & Rechnungsverwaltung</span>
|
||
</div>
|
||
</div>
|
||
|
||
<Button
|
||
onClick={() => setStep(2)}
|
||
className="w-full py-6 text-base font-bold bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 shadow-lg shadow-blue-500/15 group"
|
||
>
|
||
Setup starten
|
||
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||
</Button>
|
||
</motion.div>
|
||
)}
|
||
|
||
{/* STEP 2: Admin-Konto */}
|
||
{step === 2 && (
|
||
<motion.div
|
||
key="step2"
|
||
initial={{ opacity: 0, y: 15 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -15 }}
|
||
className="glass-dark border border-white/10 rounded-3xl p-8 space-y-6 shadow-2xl"
|
||
>
|
||
<div className="space-y-1">
|
||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
||
<ShieldCheck className="w-6 h-6 text-blue-400" />
|
||
Admin-Konto anlegen
|
||
</h2>
|
||
<p className="text-slate-400 text-sm">
|
||
Erstellen Sie das erste Administrator-Konto für vollen Systemzugriff.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Vorname *</Label>
|
||
<div className="relative">
|
||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
|
||
<Input
|
||
value={adminForm.firstName}
|
||
onChange={(e) => setAdminForm({ ...adminForm, firstName: e.target.value })}
|
||
placeholder="Vorname"
|
||
className="pl-9 bg-white/5 border-white/10 text-white placeholder:text-slate-600"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Nachname *</Label>
|
||
<div className="relative">
|
||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
|
||
<Input
|
||
value={adminForm.lastName}
|
||
onChange={(e) => setAdminForm({ ...adminForm, lastName: e.target.value })}
|
||
placeholder="Nachname"
|
||
className="pl-9 bg-white/5 border-white/10 text-white placeholder:text-slate-600"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">E-Mail-Adresse *</Label>
|
||
<div className="relative">
|
||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
|
||
<Input
|
||
type="email"
|
||
value={adminForm.email}
|
||
onChange={(e) => setAdminForm({ ...adminForm, email: e.target.value })}
|
||
placeholder="admin@firma.de"
|
||
className="pl-9 bg-white/5 border-white/10 text-white placeholder:text-slate-600"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Passwort * (min. 6 Zeichen)</Label>
|
||
<div className="relative">
|
||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
|
||
<Input
|
||
type="password"
|
||
value={adminForm.password}
|
||
onChange={(e) => setAdminForm({ ...adminForm, password: e.target.value })}
|
||
placeholder="••••••"
|
||
className="pl-9 bg-white/5 border-white/10 text-white placeholder:text-slate-600"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Passwort bestätigen *</Label>
|
||
<div className="relative">
|
||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
|
||
<Input
|
||
type="password"
|
||
value={adminForm.confirmPassword}
|
||
onChange={(e) => setAdminForm({ ...adminForm, confirmPassword: e.target.value })}
|
||
placeholder="••••••"
|
||
className="pl-9 bg-white/5 border-white/10 text-white placeholder:text-slate-600"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-3 pt-2">
|
||
<Button variant="ghost" onClick={() => setStep(1)} className="text-white hover:bg-white/5">
|
||
Zurück
|
||
</Button>
|
||
<Button
|
||
onClick={() => setStep(3)}
|
||
disabled={!isAdminFormValid}
|
||
className="flex-1 bg-blue-600 hover:bg-blue-500 text-white font-bold"
|
||
>
|
||
Weiter zu Firmendaten
|
||
<ArrowRight className="w-4 h-4 ml-2" />
|
||
</Button>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
|
||
{/* STEP 3: Firmendaten & Rechnungsadresse */}
|
||
{step === 3 && (
|
||
<motion.div
|
||
key="step3"
|
||
initial={{ opacity: 0, y: 15 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -15 }}
|
||
className="glass-dark border border-white/10 rounded-3xl p-8 space-y-6 shadow-2xl"
|
||
>
|
||
<div className="space-y-1">
|
||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
||
<Building className="w-6 h-6 text-blue-400" />
|
||
Firmendaten & Adressen
|
||
</h2>
|
||
<p className="text-slate-400 text-sm">
|
||
Tragen Sie Ihren Firmennamen, die Anschrift und die Rechnungsadresse ein.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Firmenname *</Label>
|
||
<div className="relative">
|
||
<Building className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500" />
|
||
<Input
|
||
value={brandingForm.companyName}
|
||
onChange={(e) => setBrandingForm({ ...brandingForm, companyName: e.target.value })}
|
||
placeholder="z. B. CASPOS Software GmbH"
|
||
className="pl-9 bg-white/5 border-white/10 text-white placeholder:text-slate-600"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Anschrift */}
|
||
<div className="space-y-3 pt-1 border-t border-slate-800">
|
||
<span className="text-xs font-bold text-slate-300 flex items-center gap-1.5">
|
||
<MapPin className="w-3.5 h-3.5 text-blue-400" /> Firmenanschrift
|
||
</span>
|
||
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Straße & Hausnummer *</Label>
|
||
<Input
|
||
value={brandingForm.street}
|
||
onChange={(e) => setBrandingForm({ ...brandingForm, street: e.target.value })}
|
||
placeholder="Musterstraße 12"
|
||
className="bg-white/5 border-white/10 text-white placeholder:text-slate-600 text-xs"
|
||
/>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-3 gap-3">
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">PLZ *</Label>
|
||
<Input
|
||
value={brandingForm.zip}
|
||
onChange={(e) => setBrandingForm({ ...brandingForm, zip: e.target.value })}
|
||
placeholder="12345"
|
||
className="bg-white/5 border-white/10 text-white text-xs"
|
||
/>
|
||
</div>
|
||
<div className="col-span-2 space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Ort *</Label>
|
||
<Input
|
||
value={brandingForm.city}
|
||
onChange={(e) => setBrandingForm({ ...brandingForm, city: e.target.value })}
|
||
placeholder="Musterstadt"
|
||
className="bg-white/5 border-white/10 text-white text-xs"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Rechnungsadresse */}
|
||
<div className="space-y-3 pt-2 border-t border-slate-800">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs font-bold text-slate-300 flex items-center gap-1.5">
|
||
<Receipt className="w-3.5 h-3.5 text-blue-400" /> Rechnungsadresse
|
||
</span>
|
||
<label className="flex items-center gap-2 text-xs text-slate-400 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={brandingForm.sameBillingAddress}
|
||
onChange={(e) =>
|
||
setBrandingForm({ ...brandingForm, sameBillingAddress: e.target.checked })
|
||
}
|
||
className="w-4 h-4 rounded border-slate-700 bg-white/5 text-blue-500"
|
||
/>
|
||
Gleiche wie Anschrift
|
||
</label>
|
||
</div>
|
||
|
||
{!brandingForm.sameBillingAddress && (
|
||
<div className="space-y-3 p-3 rounded-xl bg-white/5 border border-white/10">
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Rechnungsstraße & Nr. *</Label>
|
||
<Input
|
||
value={brandingForm.billingStreet}
|
||
onChange={(e) =>
|
||
setBrandingForm({ ...brandingForm, billingStreet: e.target.value })
|
||
}
|
||
placeholder="Rechnungsstraße 45"
|
||
className="bg-white/5 border-white/10 text-white text-xs"
|
||
/>
|
||
</div>
|
||
<div className="grid grid-cols-3 gap-3">
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">PLZ *</Label>
|
||
<Input
|
||
value={brandingForm.billingZip}
|
||
onChange={(e) =>
|
||
setBrandingForm({ ...brandingForm, billingZip: e.target.value })
|
||
}
|
||
placeholder="54321"
|
||
className="bg-white/5 border-white/10 text-white text-xs"
|
||
/>
|
||
</div>
|
||
<div className="col-span-2 space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Ort *</Label>
|
||
<Input
|
||
value={brandingForm.billingCity}
|
||
onChange={(e) =>
|
||
setBrandingForm({ ...brandingForm, billingCity: e.target.value })
|
||
}
|
||
placeholder="Rechnungsstadt"
|
||
className="bg-white/5 border-white/10 text-white text-xs"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-3 pt-2">
|
||
<Button variant="ghost" onClick={() => setStep(2)} className="text-white hover:bg-white/5">
|
||
Zurück
|
||
</Button>
|
||
<Button
|
||
onClick={() => setStep(4)}
|
||
disabled={!isCompanyFormValid}
|
||
className="flex-1 bg-blue-600 hover:bg-blue-500 text-white font-bold"
|
||
>
|
||
Weiter zum Farbschema
|
||
<ArrowRight className="w-4 h-4 ml-2" />
|
||
</Button>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
|
||
{/* STEP 4: Farbschema & Live-Vorschau */}
|
||
{step === 4 && (
|
||
<motion.div
|
||
key="step4"
|
||
initial={{ opacity: 0, y: 15 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -15 }}
|
||
className="glass-dark border border-white/10 rounded-3xl p-8 space-y-6 shadow-2xl"
|
||
>
|
||
<div className="space-y-1">
|
||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
||
<Palette className="w-6 h-6 text-blue-400" />
|
||
Farbschema & Webshop Styling
|
||
</h2>
|
||
<p className="text-slate-400 text-sm">
|
||
Wählen Sie aus 9 vorgegebenen Farbpaletten oder erstellen Sie eine eigene Farbkombination mit Live-Vorschau.
|
||
</p>
|
||
</div>
|
||
|
||
<ColorThemePicker
|
||
colorScheme={brandingForm.colorScheme}
|
||
primaryColor={brandingForm.primaryColor}
|
||
accentColor={brandingForm.accentColor}
|
||
companyName={brandingForm.companyName}
|
||
onChange={(scheme, primary, accent) =>
|
||
setBrandingForm({
|
||
...brandingForm,
|
||
colorScheme: scheme,
|
||
primaryColor: primary,
|
||
accentColor: accent,
|
||
})
|
||
}
|
||
/>
|
||
|
||
<div className="flex gap-3 pt-2">
|
||
<Button variant="ghost" onClick={() => setStep(3)} className="text-white hover:bg-white/5">
|
||
Zurück
|
||
</Button>
|
||
<Button
|
||
onClick={() => setStep(5)}
|
||
className="flex-1 bg-blue-600 hover:bg-blue-500 text-white font-bold"
|
||
>
|
||
Weiter zu SMTP
|
||
<ArrowRight className="w-4 h-4 ml-2" />
|
||
</Button>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
|
||
{/* STEP 5: SMTP Mailserver */}
|
||
{step === 5 && (
|
||
<motion.div
|
||
key="step5"
|
||
initial={{ opacity: 0, y: 15 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
exit={{ opacity: 0, y: -15 }}
|
||
className="glass-dark border border-white/10 rounded-3xl p-8 space-y-6 shadow-2xl"
|
||
>
|
||
<div className="space-y-1">
|
||
<div className="flex items-center justify-between">
|
||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
||
<Mail className="w-6 h-6 text-blue-400" />
|
||
SMTP-Mailserver
|
||
</h2>
|
||
<span className="text-[10px] uppercase font-bold tracking-wider px-2.5 py-1 rounded-full bg-slate-800 text-amber-400 border border-amber-500/20">
|
||
Optional
|
||
</span>
|
||
</div>
|
||
<p className="text-slate-400 text-sm">
|
||
Tragen Sie Ihre Mailserver-Daten ein oder überspringen Sie diesen Schritt. Sie können die Einstellungen jederzeit im Admin-Bereich anpassen.
|
||
</p>
|
||
</div>
|
||
|
||
{errorMsg && (
|
||
<div className="p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-red-400 text-xs">
|
||
{errorMsg}
|
||
</div>
|
||
)}
|
||
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-3 gap-4">
|
||
<div className="col-span-2 space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">SMTP Host</Label>
|
||
<Input
|
||
value={smtpForm.host}
|
||
onChange={(e) => setSmtpForm({ ...smtpForm, host: e.target.value })}
|
||
placeholder="smtp.domain.de"
|
||
className="bg-white/5 border-white/10 text-white placeholder:text-slate-600"
|
||
/>
|
||
</div>
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">SMTP Port</Label>
|
||
<Input
|
||
value={smtpForm.port}
|
||
onChange={(e) => setSmtpForm({ ...smtpForm, port: e.target.value })}
|
||
placeholder="587"
|
||
className="bg-white/5 border-white/10 text-white"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Benutzername</Label>
|
||
<Input
|
||
value={smtpForm.user}
|
||
onChange={(e) => setSmtpForm({ ...smtpForm, user: e.target.value })}
|
||
placeholder="mail@domain.de"
|
||
className="bg-white/5 border-white/10 text-white placeholder:text-slate-600"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-1.5">
|
||
<Label className="text-slate-300 text-xs">Passwort</Label>
|
||
<Input
|
||
type="password"
|
||
value={smtpForm.pass}
|
||
onChange={(e) => setSmtpForm({ ...smtpForm, pass: e.target.value })}
|
||
placeholder="••••••••••••"
|
||
className="bg-white/5 border-white/10 text-white placeholder:text-slate-600"
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between pt-2">
|
||
<div className="flex items-center gap-2">
|
||
<input
|
||
type="checkbox"
|
||
id="smtp_secure"
|
||
checked={smtpForm.secure}
|
||
onChange={(e) => setSmtpForm({ ...smtpForm, secure: e.target.checked })}
|
||
className="w-4 h-4 rounded border-slate-700 bg-white/5 text-blue-500 focus:ring-0 focus:ring-offset-0"
|
||
/>
|
||
<Label htmlFor="smtp_secure" className="text-slate-300 text-sm cursor-pointer select-none">
|
||
Sichere Verbindung (SSL/TLS)
|
||
</Label>
|
||
</div>
|
||
|
||
<Button
|
||
type="button"
|
||
variant="secondary"
|
||
size="sm"
|
||
onClick={handleTestSmtp}
|
||
disabled={testSmtpLoading || !smtpForm.host || !smtpForm.user}
|
||
className="bg-white/10 hover:bg-white/20 text-white text-xs border border-white/10 flex items-center gap-1.5"
|
||
>
|
||
{testSmtpLoading ? (
|
||
<>
|
||
<Loader2 className="w-3.5 h-3.5 animate-spin" /> Testen...
|
||
</>
|
||
) : (
|
||
<>
|
||
<Send className="w-3.5 h-3.5 text-blue-400" /> Test-E-Mail senden
|
||
</>
|
||
)}
|
||
</Button>
|
||
</div>
|
||
|
||
{testSmtpResult && (
|
||
<div
|
||
className={`p-3 rounded-lg border text-xs font-medium ${
|
||
testSmtpResult.success
|
||
? 'bg-emerald-500/10 border-emerald-500/30 text-emerald-400'
|
||
: 'bg-red-500/10 border-red-500/30 text-red-400'
|
||
}`}
|
||
>
|
||
{testSmtpResult.message}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex flex-col sm:flex-row gap-3 pt-2">
|
||
<Button
|
||
variant="ghost"
|
||
onClick={() => setStep(4)}
|
||
disabled={loading}
|
||
className="text-white hover:bg-white/5"
|
||
>
|
||
Zurück
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
onClick={handleFinishSetup}
|
||
disabled={loading}
|
||
className="border-slate-700 text-slate-300 hover:text-white"
|
||
>
|
||
Überspringen
|
||
</Button>
|
||
<Button
|
||
onClick={handleFinishSetup}
|
||
disabled={loading}
|
||
className="flex-1 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 text-white font-bold"
|
||
>
|
||
{loading ? (
|
||
<>
|
||
<Loader2 className="w-4 h-4 mr-2 animate-spin" /> Setup wird abgeschlossen...
|
||
</>
|
||
) : (
|
||
'Einrichtung abschließen'
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|