refactor(admin): optimize smtp settings with bento grid

This commit is contained in:
DanielS
2026-08-06 17:49:37 +02:00
parent abd8f54ef7
commit d02703af8b

View File

@@ -1,9 +1,13 @@
"use client"; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { motion } from 'framer-motion';
import { createClient } from '@/lib/supabase/client'; import { createClient } from '@/lib/supabase/client';
import { Loader2 } from 'lucide-react'; import { Loader2, Mail, Server, ShieldCheck, Key, Send, CheckCircle2, AlertCircle } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
interface Settings { interface Settings {
host: string; host: string;
@@ -13,39 +17,60 @@ interface Settings {
pass: string; pass: string;
} }
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.1 }
}
};
const itemVariants = {
hidden: { opacity: 0, y: 15 },
visible: { opacity: 1, y: 0, transition: { type: 'spring' as const, stiffness: 80, damping: 15 } }
};
export default function SettingsPage() { export default function SettingsPage() {
const [settings, setSettings] = useState<Settings | null>(null); const [settings, setSettings] = useState<Settings | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const [msgType, setMsgType] = useState<'success' | 'error' | ''>('');
const [saving, setSaving] = useState(false);
const [testing, setTesting] = useState(false);
const router = useRouter(); const router = useRouter();
// Load current settings and verify access
useEffect(() => { useEffect(() => {
async function checkAccessAndLoad() { async function checkAccessAndLoad() {
const supabase = createClient(); try {
const { data: { user } } = await supabase.auth.getUser(); const supabase = createClient();
if (!user) { const { data: { user } } = await supabase.auth.getUser();
router.push('/auth/login'); if (!user) {
return; router.push('/auth/login');
} return;
const { data: userData } = await supabase }
.from('users')
.select('role')
.eq('id', user.id)
.single();
if (!userData || userData.role === 'verwaltung') {
router.push('/admin');
return;
}
fetch('/api/admin/smtp-settings') const [userRes, smtpRes] = await Promise.all([
.then((res) => res.json()) supabase.from('users').select('role').eq('id', user.id).single(),
.then((data) => { fetch('/api/admin/smtp-settings').then(res => res.json())
if (data.settings) setSettings(data.settings as Settings); ]);
else setMessage('Failed to load settings');
}) if (!userRes.data || userRes.data.role === 'verwaltung') {
.catch(() => setMessage('Failed to load settings')) router.push('/admin');
.finally(() => setLoading(false)); return;
}
if (smtpRes.settings) {
setSettings(smtpRes.settings as Settings);
} else {
setMessage('Fehler beim Laden der SMTP-Einstellungen.');
setMsgType('error');
}
} catch (err) {
setMessage('Fehler beim Laden der Einstellungen.');
setMsgType('error');
} finally {
setLoading(false);
}
} }
checkAccessAndLoad(); checkAccessAndLoad();
}, [router]); }, [router]);
@@ -62,110 +87,195 @@ export default function SettingsPage() {
const handleSave = async (e: React.FormEvent) => { const handleSave = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setSaving(true);
setMessage(''); setMessage('');
const res = await fetch('/api/admin/smtp-settings', { try {
method: 'POST', const res = await fetch('/api/admin/smtp-settings', {
headers: { 'Content-Type': 'application/json' }, method: 'POST',
body: JSON.stringify(settings), headers: { 'Content-Type': 'application/json' },
}); body: JSON.stringify(settings),
const data = await res.json(); });
if (res.ok) setMessage('Settings saved'); const data = await res.json();
else setMessage(data.error || 'Error saving settings'); if (res.ok) {
setMessage('SMTP-Einstellungen erfolgreich gespeichert.');
setMsgType('success');
} else {
setMessage(data.error || 'Fehler beim Speichern.');
setMsgType('error');
}
} catch {
setMessage('Verbindungsfehler beim Speichern.');
setMsgType('error');
} finally {
setSaving(false);
}
}; };
const handleTestEmail = async () => { const handleTestEmail = async () => {
setTesting(true);
setMessage(''); setMessage('');
const testPayload = { try {
to: settings?.user || '', // send to the configured user address const testPayload = {
subject: 'Test Email from Webshop Admin', to: settings?.user || '',
text: 'This is a test email to verify SMTP configuration.', subject: 'Test-E-Mail aus dem CASPOS Webshop Admin',
}; text: 'Dies ist eine automatische Test-E-Mail zur Bestätigung der SMTP-Konfiguration.',
const res = await fetch('/api/admin/send-test-email', { };
method: 'POST', const res = await fetch('/api/admin/send-test-email', {
headers: { 'Content-Type': 'application/json' }, method: 'POST',
body: JSON.stringify(testPayload), headers: { 'Content-Type': 'application/json' },
}); body: JSON.stringify(testPayload),
const data = await res.json(); });
if (res.ok) setMessage('Test email sent successfully'); const data = await res.json();
else setMessage(data.error || 'Failed to send test email'); if (res.ok) {
setMessage('Test-E-Mail erfolgreich versendet.');
setMsgType('success');
} else {
setMessage(data.error || 'Versand der Test-E-Mail fehlgeschlagen.');
setMsgType('error');
}
} catch {
setMessage('Fehler beim Senden der Test-E-Mail.');
setMsgType('error');
} finally {
setTesting(false);
}
}; };
if (loading) return <div className="p-8 text-white flex justify-center items-center"><Loader2 className="w-8 h-8 animate-spin text-primary" /></div>; if (loading) {
return <div className="p-8 text-white flex justify-center items-center"><Loader2 className="w-8 h-8 animate-spin text-primary" /></div>;
}
return ( return (
<div className="max-w-2xl mx-auto p-8 bg-black/30 backdrop-blur-xl rounded-lg text-white"> <div className="p-6 max-w-5xl mx-auto text-slate-900 dark:text-white space-y-6">
<h1 className="text-2xl font-bold mb-6">SMTPEinstellungen</h1> {/* Header */}
{message && <p className="mb-4 text-yellow-300">{message}</p>} <motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} className="space-y-1">
<form onSubmit={handleSave} className="space-y-4"> <h1 className="text-3xl font-extrabold tracking-tight flex items-center gap-3">
<label className="flex flex-col"> <Mail className="w-8 h-8 text-sky-400" />
Host SMTP-Einstellungen
<input </h1>
name="host" <p className="text-slate-400 text-xs">
type="text" Konfigurieren Sie den E-Mail-Server für transaktionale Benachrichtigungen und Bestellbestätigungen.
required </p>
value={settings?.host ?? ''} </motion.div>
onChange={handleChange}
className="mt-1 p-2 rounded bg-black/20 border border-white/20" {/* Dynamic Status Message */}
/> {message && (
</label> <motion.div
<label className="flex flex-col"> initial={{ opacity: 0, height: 0 }}
Port animate={{ opacity: 1, height: 'auto' }}
<input className={`p-3.5 rounded-xl text-xs font-semibold border flex items-center gap-2 ${
name="port" msgType === 'success'
type="number" ? 'bg-emerald-500/10 border-emerald-500/20 text-emerald-400'
required : 'bg-destructive/10 border-destructive/20 text-destructive'
value={settings?.port ?? ''} }`}
onChange={handleChange} >
className="mt-1 p-2 rounded bg-black/20 border border-white/20" {msgType === 'success' ? <CheckCircle2 className="w-4 h-4 shrink-0" /> : <AlertCircle className="w-4 h-4 shrink-0" />}
/> {message}
</label> </motion.div>
<label className="inline-flex items-center space-x-2"> )}
<input
name="secure" {/* Bento Grid layout for SMTP */}
type="checkbox" <form onSubmit={handleSave}>
checked={settings?.secure ?? false} <motion.div variants={containerVariants} initial="hidden" animate="visible" className="grid grid-cols-1 md:grid-cols-3 gap-5">
onChange={handleChange} {/* Bento Box 1: Server Connection Details (2 Spalten) */}
className="rounded" <motion.div variants={itemVariants} className="md:col-span-2 p-6 rounded-2xl bg-slate-900/60 border border-slate-800 backdrop-blur-md space-y-4 shadow-md">
/> <div className="flex items-center gap-2 text-sky-400 font-bold text-sm border-b border-slate-800 pb-3">
<span>SSL / TLS (secure)</span> <Server className="w-4 h-4" />
</label> <span>Server & Serververbindung</span>
<label className="flex flex-col"> </div>
Benutzer (SMTPUser)
<input <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
name="user" <div className="sm:col-span-2 space-y-1.5">
type="email" <Label htmlFor="host" className="text-xs font-bold text-slate-300">SMTP Host</Label>
required <Input
value={settings?.user ?? ''} id="host"
onChange={handleChange} name="host"
className="mt-1 p-2 rounded bg-black/20 border border-white/20" type="text"
/> required
</label> value={settings?.host ?? ''}
<label className="flex flex-col"> onChange={handleChange}
Passwort placeholder="smtp.beispiel.de"
<input className="bg-slate-950/80 border-slate-800 text-white rounded-xl font-mono text-sm"
name="pass" />
type="password" </div>
required
value={settings?.pass ?? ''} <div className="space-y-1.5">
onChange={handleChange} <Label htmlFor="port" className="text-xs font-bold text-slate-300">Port</Label>
className="mt-1 p-2 rounded bg-black/20 border border-white/20" <Input
/> id="port"
</label> name="port"
<div className="flex space-x-4 mt-4"> type="number"
<button required
type="submit" value={settings?.port ?? ''}
className="px-4 py-2 bg-primary rounded hover:bg-primary/80 transition" onChange={handleChange}
> placeholder="587"
Speichern className="bg-slate-950/80 border-slate-800 text-white rounded-xl font-mono text-sm"
</button> />
<button </div>
type="button" </div>
onClick={handleTestEmail}
className="px-4 py-2 bg-green-600 rounded hover:bg-green-500 transition" <div className="pt-2">
> <label className="inline-flex items-center gap-2 cursor-pointer p-3 rounded-xl bg-slate-950/50 border border-slate-800/80 hover:bg-slate-800/40 transition">
TestMail senden <input
</button> name="secure"
</div> type="checkbox"
checked={settings?.secure ?? false}
onChange={handleChange}
className="w-4 h-4 rounded text-sky-500 bg-slate-900 border-slate-700"
/>
<span className="text-xs font-semibold text-slate-200">SSL / TLS Verschlüsselung aktivieren</span>
</label>
</div>
</motion.div>
{/* Bento Box 2: Auth Credentials (1 Spalte) */}
<motion.div variants={itemVariants} className="p-6 rounded-2xl bg-slate-900/60 border border-slate-800 backdrop-blur-md space-y-4 shadow-md flex flex-col justify-between">
<div className="space-y-4">
<div className="flex items-center gap-2 text-sky-400 font-bold text-sm border-b border-slate-800 pb-3">
<Key className="w-4 h-4" />
<span>Zugangsdaten</span>
</div>
<div className="space-y-1.5">
<Label htmlFor="user" className="text-xs font-bold text-slate-300">SMTP Benutzer</Label>
<Input
id="user"
name="user"
type="email"
required
value={settings?.user ?? ''}
onChange={handleChange}
placeholder="absender@beispiel.de"
className="bg-slate-950/80 border-slate-800 text-white rounded-xl font-mono text-sm"
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="pass" className="text-xs font-bold text-slate-300">Passwort</Label>
<Input
id="pass"
name="pass"
type="password"
required
value={settings?.pass ?? ''}
onChange={handleChange}
placeholder="••••••••••••"
className="bg-slate-950/80 border-slate-800 text-white rounded-xl font-mono text-sm"
/>
</div>
</div>
{/* Submit & Test Buttons */}
<div className="flex flex-col gap-2 pt-4 border-t border-slate-800">
<Button type="submit" disabled={saving || testing} className="w-full bg-sky-600 hover:bg-sky-500 text-white rounded-xl h-10 text-xs font-bold">
{saving ? <><Loader2 className="w-4 h-4 mr-2 animate-spin" /> Speichert...</> : 'Konfiguration speichern'}
</Button>
<Button type="button" onClick={handleTestEmail} disabled={saving || testing} variant="outline" className="w-full border-slate-700 text-slate-300 hover:text-white rounded-xl h-10 text-xs font-bold">
{testing ? <><Loader2 className="w-4 h-4 mr-2 animate-spin" /> Sendet...</> : <><Send className="w-3.5 h-3.5 mr-2 text-emerald-400" /> Test-Mail senden</>}
</Button>
</div>
</motion.div>
</motion.div>
</form> </form>
</div> </div>
); );