406 lines
17 KiB
TypeScript
406 lines
17 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 } 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 } from '@/lib/actions/setup'
|
|
|
|
export function SetupWizard() {
|
|
const router = useRouter()
|
|
const [step, setStep] = useState(1)
|
|
const [loading, setLoading] = useState(false)
|
|
const [errorMsg, setErrorMsg] = useState('')
|
|
|
|
// Step 2 Form: Admin account
|
|
const [adminForm, setAdminForm] = useState({
|
|
email: '',
|
|
password: '',
|
|
confirmPassword: '',
|
|
companyName: '',
|
|
firstName: '',
|
|
lastName: '',
|
|
})
|
|
|
|
// Step 3 Form: SMTP Config
|
|
const [smtpForm, setSmtpForm] = useState({
|
|
host: '',
|
|
port: '587',
|
|
secure: false,
|
|
user: '',
|
|
pass: '',
|
|
})
|
|
|
|
// Validation checks for Admin form
|
|
const isAdminFormValid =
|
|
adminForm.email.includes('@') &&
|
|
adminForm.password.length >= 6 &&
|
|
adminForm.password === adminForm.confirmPassword &&
|
|
adminForm.companyName.trim().length > 0 &&
|
|
adminForm.firstName.trim().length > 0 &&
|
|
adminForm.lastName.trim().length > 0
|
|
|
|
// Validation checks for SMTP form
|
|
const isSmtpFormValid =
|
|
smtpForm.host.trim().length > 0 &&
|
|
!isNaN(Number(smtpForm.port)) &&
|
|
smtpForm.user.trim().length > 0
|
|
|
|
const handleFinishSetup = async () => {
|
|
if (!isAdminFormValid || !isSmtpFormValid) return
|
|
setLoading(true)
|
|
setErrorMsg('')
|
|
|
|
try {
|
|
// 1. Submit details via server action
|
|
const res = await completeSetup(
|
|
{
|
|
email: adminForm.email,
|
|
password: adminForm.password,
|
|
companyName: adminForm.companyName,
|
|
firstName: adminForm.firstName,
|
|
lastName: adminForm.lastName,
|
|
},
|
|
{
|
|
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
|
|
}
|
|
|
|
// 2. Automatischer Login für flüssiges Erlebnis
|
|
const supabase = createClient()
|
|
const { error: loginError } = await supabase.auth.signInWithPassword({
|
|
email: adminForm.email,
|
|
password: adminForm.password,
|
|
})
|
|
|
|
if (loginError) {
|
|
console.error('Auto login failed:', loginError)
|
|
// Redirect anyway since setup is done
|
|
}
|
|
|
|
// 3. Weiterleitung
|
|
router.push('/')
|
|
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">
|
|
{/* Dynamic 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-xl relative z-10">
|
|
{/* Step Indicator Header */}
|
|
<div className="flex items-center justify-between mb-8 px-2">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xl font-bold tracking-tight text-gradient bg-gradient-to-r from-blue-400 to-indigo-400">CASPOS</span>
|
|
<span className="text-xs px-2 py-0.5 rounded-full bg-blue-500/10 text-blue-400 border border-blue-500/20 font-medium">Initialisierung</span>
|
|
</div>
|
|
<div className="flex gap-1.5">
|
|
{[1, 2, 3].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 && (
|
|
<motion.div
|
|
key="step1"
|
|
initial={{ opacity: 0, y: 15 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -15 }}
|
|
transition={{ duration: 0.3 }}
|
|
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">Willkommen bei CASPOS!</h1>
|
|
<p className="text-slate-400 leading-relaxed">
|
|
Richten Sie Ihren persönlichen Lizenz- und Anfrage-Shop in wenigen Schritten ein. Wir konfigurieren Ihr Administrator-Konto und die Mailverbindung.
|
|
</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>Automatische Schema-Updates auf dem neuesten Stand</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Check className="w-4 h-4 text-green-400 shrink-0" />
|
|
<span>SMTP Mailversand für direkte Anfragebestätigungen</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 && (
|
|
<motion.div
|
|
key="step2"
|
|
initial={{ opacity: 0, y: 15 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -15 }}
|
|
transition={{ duration: 0.3 }}
|
|
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 den ersten Administrator-Benutzer. Dieser erhält vollen Zugriff auf das System.
|
|
</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">Unternehmen / Partnername *</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={adminForm.companyName}
|
|
onChange={(e) => setAdminForm({ ...adminForm, companyName: e.target.value })}
|
|
placeholder="Name Ihrer Firma"
|
|
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">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">
|
|
<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 SMTP
|
|
<ArrowRight className="w-4 h-4 ml-2" />
|
|
</Button>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
|
|
{step === 3 && (
|
|
<motion.div
|
|
key="step3"
|
|
initial={{ opacity: 0, y: 15 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -15 }}
|
|
transition={{ duration: 0.3 }}
|
|
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">
|
|
<Mail className="w-6 h-6 text-blue-400" />
|
|
SMTP-Mailserver konfigurieren
|
|
</h2>
|
|
<p className="text-slate-400 text-sm">
|
|
Tragen Sie Ihre Mailserver-Daten ein, um automatisierte E-Mails an Partner und Kunden senden zu können.
|
|
</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 gap-2 pt-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 anstelle STARTTLS)
|
|
</Label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-3">
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => setStep(2)}
|
|
disabled={loading}
|
|
className="text-white hover:bg-white/5"
|
|
>
|
|
Zurück
|
|
</Button>
|
|
<Button
|
|
onClick={handleFinishSetup}
|
|
disabled={!isSmtpFormValid || 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>
|
|
)
|
|
}
|