refactor(admin): optimize smtp settings with bento grid
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { motion } from 'framer-motion';
|
||||
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 {
|
||||
host: string;
|
||||
@@ -13,39 +17,60 @@ interface Settings {
|
||||
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() {
|
||||
const [settings, setSettings] = useState<Settings | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [message, setMessage] = useState('');
|
||||
const [msgType, setMsgType] = useState<'success' | 'error' | ''>('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [testing, setTesting] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
// Load current settings and verify access
|
||||
useEffect(() => {
|
||||
async function checkAccessAndLoad() {
|
||||
const supabase = createClient();
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
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;
|
||||
}
|
||||
try {
|
||||
const supabase = createClient();
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
router.push('/auth/login');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/admin/smtp-settings')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data.settings) setSettings(data.settings as Settings);
|
||||
else setMessage('Failed to load settings');
|
||||
})
|
||||
.catch(() => setMessage('Failed to load settings'))
|
||||
.finally(() => setLoading(false));
|
||||
const [userRes, smtpRes] = await Promise.all([
|
||||
supabase.from('users').select('role').eq('id', user.id).single(),
|
||||
fetch('/api/admin/smtp-settings').then(res => res.json())
|
||||
]);
|
||||
|
||||
if (!userRes.data || userRes.data.role === 'verwaltung') {
|
||||
router.push('/admin');
|
||||
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();
|
||||
}, [router]);
|
||||
@@ -62,110 +87,195 @@ export default function SettingsPage() {
|
||||
|
||||
const handleSave = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setSaving(true);
|
||||
setMessage('');
|
||||
const res = await fetch('/api/admin/smtp-settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) setMessage('Settings saved');
|
||||
else setMessage(data.error || 'Error saving settings');
|
||||
try {
|
||||
const res = await fetch('/api/admin/smtp-settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
const data = await res.json();
|
||||
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 () => {
|
||||
setTesting(true);
|
||||
setMessage('');
|
||||
const testPayload = {
|
||||
to: settings?.user || '', // send to the configured user address
|
||||
subject: 'Test Email from Webshop Admin',
|
||||
text: 'This is a test email to verify SMTP configuration.',
|
||||
};
|
||||
const res = await fetch('/api/admin/send-test-email', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(testPayload),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) setMessage('Test email sent successfully');
|
||||
else setMessage(data.error || 'Failed to send test email');
|
||||
try {
|
||||
const testPayload = {
|
||||
to: settings?.user || '',
|
||||
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',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(testPayload),
|
||||
});
|
||||
const data = await res.json();
|
||||
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 (
|
||||
<div className="max-w-2xl mx-auto p-8 bg-black/30 backdrop-blur-xl rounded-lg text-white">
|
||||
<h1 className="text-2xl font-bold mb-6">SMTP‑Einstellungen</h1>
|
||||
{message && <p className="mb-4 text-yellow-300">{message}</p>}
|
||||
<form onSubmit={handleSave} className="space-y-4">
|
||||
<label className="flex flex-col">
|
||||
Host
|
||||
<input
|
||||
name="host"
|
||||
type="text"
|
||||
required
|
||||
value={settings?.host ?? ''}
|
||||
onChange={handleChange}
|
||||
className="mt-1 p-2 rounded bg-black/20 border border-white/20"
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-col">
|
||||
Port
|
||||
<input
|
||||
name="port"
|
||||
type="number"
|
||||
required
|
||||
value={settings?.port ?? ''}
|
||||
onChange={handleChange}
|
||||
className="mt-1 p-2 rounded bg-black/20 border border-white/20"
|
||||
/>
|
||||
</label>
|
||||
<label className="inline-flex items-center space-x-2">
|
||||
<input
|
||||
name="secure"
|
||||
type="checkbox"
|
||||
checked={settings?.secure ?? false}
|
||||
onChange={handleChange}
|
||||
className="rounded"
|
||||
/>
|
||||
<span>SSL / TLS (secure)</span>
|
||||
</label>
|
||||
<label className="flex flex-col">
|
||||
Benutzer (SMTP‑User)
|
||||
<input
|
||||
name="user"
|
||||
type="email"
|
||||
required
|
||||
value={settings?.user ?? ''}
|
||||
onChange={handleChange}
|
||||
className="mt-1 p-2 rounded bg-black/20 border border-white/20"
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-col">
|
||||
Passwort
|
||||
<input
|
||||
name="pass"
|
||||
type="password"
|
||||
required
|
||||
value={settings?.pass ?? ''}
|
||||
onChange={handleChange}
|
||||
className="mt-1 p-2 rounded bg-black/20 border border-white/20"
|
||||
/>
|
||||
</label>
|
||||
<div className="flex space-x-4 mt-4">
|
||||
<button
|
||||
type="submit"
|
||||
className="px-4 py-2 bg-primary rounded hover:bg-primary/80 transition"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleTestEmail}
|
||||
className="px-4 py-2 bg-green-600 rounded hover:bg-green-500 transition"
|
||||
>
|
||||
Test‑Mail senden
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-6 max-w-5xl mx-auto text-slate-900 dark:text-white space-y-6">
|
||||
{/* Header */}
|
||||
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} className="space-y-1">
|
||||
<h1 className="text-3xl font-extrabold tracking-tight flex items-center gap-3">
|
||||
<Mail className="w-8 h-8 text-sky-400" />
|
||||
SMTP-Einstellungen
|
||||
</h1>
|
||||
<p className="text-slate-400 text-xs">
|
||||
Konfigurieren Sie den E-Mail-Server für transaktionale Benachrichtigungen und Bestellbestätigungen.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Dynamic Status Message */}
|
||||
{message && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
className={`p-3.5 rounded-xl text-xs font-semibold border flex items-center gap-2 ${
|
||||
msgType === 'success'
|
||||
? 'bg-emerald-500/10 border-emerald-500/20 text-emerald-400'
|
||||
: 'bg-destructive/10 border-destructive/20 text-destructive'
|
||||
}`}
|
||||
>
|
||||
{msgType === 'success' ? <CheckCircle2 className="w-4 h-4 shrink-0" /> : <AlertCircle className="w-4 h-4 shrink-0" />}
|
||||
{message}
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Bento Grid layout for SMTP */}
|
||||
<form onSubmit={handleSave}>
|
||||
<motion.div variants={containerVariants} initial="hidden" animate="visible" className="grid grid-cols-1 md:grid-cols-3 gap-5">
|
||||
{/* Bento Box 1: Server Connection Details (2 Spalten) */}
|
||||
<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">
|
||||
<Server className="w-4 h-4" />
|
||||
<span>Server & Serververbindung</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<div className="sm:col-span-2 space-y-1.5">
|
||||
<Label htmlFor="host" className="text-xs font-bold text-slate-300">SMTP Host</Label>
|
||||
<Input
|
||||
id="host"
|
||||
name="host"
|
||||
type="text"
|
||||
required
|
||||
value={settings?.host ?? ''}
|
||||
onChange={handleChange}
|
||||
placeholder="smtp.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="port" className="text-xs font-bold text-slate-300">Port</Label>
|
||||
<Input
|
||||
id="port"
|
||||
name="port"
|
||||
type="number"
|
||||
required
|
||||
value={settings?.port ?? ''}
|
||||
onChange={handleChange}
|
||||
placeholder="587"
|
||||
className="bg-slate-950/80 border-slate-800 text-white rounded-xl font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<input
|
||||
name="secure"
|
||||
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>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user