'use client'; import { useState, useEffect, useCallback } from 'react'; interface Settings { title?: string; subtitle?: string; lang?: string; exifOnHover?: boolean; map?: boolean; transitions?: boolean; theme?: { preset?: string; accent?: string; photoFrame?: string; grain?: boolean; headerDot?: boolean; heroStyle?: string; }; grid?: { columns?: number; gap?: number; aspectRatio?: string; layout?: string; }; footer?: { name?: string; instagram?: string; email?: string; website?: string; }; legal?: { enabled?: boolean; name?: string; address?: string; zipCity?: string; country?: string; email?: string; phone?: string; taxId?: string; vatId?: string; extraInfo?: string; }; seo?: { title?: string; description?: string; noIndex?: boolean; noFollow?: boolean; }; } const PRESETS = ['studio', 'minimal', 'editorial', 'classic', 'noir', 'monograph']; const LAYOUTS = ['masonry', 'uniform', 'showcase', 'filmstrip', 'editorial-flow']; const PHOTO_FRAMES = ['none', 'passepartout', 'shadow']; const HERO_STYLES = ['split', 'fullbleed', 'minimal', 'stacked', 'typographic', 'mosaic']; const ASPECT_RATIOS = ['1', '3/2', '2/3', '16/9', 'auto']; export default function SettingsEditor() { const [settings, setSettings] = useState({}); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [dirty, setDirty] = useState(false); const [saveMessage, setSaveMessage] = useState(''); const [activeSection, setActiveSection] = useState('general'); useEffect(() => { loadSettings(); }, []); // ── Keyboard shortcut: ⌘+S / Ctrl+S ───────────────────────── const handleSaveRef = useCallback(() => { if (dirty && !saving) { handleSave(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [dirty, saving, settings]); useEffect(() => { function handleKeyDown(e: KeyboardEvent) { if ((e.metaKey || e.ctrlKey) && e.key === 's') { e.preventDefault(); handleSaveRef(); } } window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [handleSaveRef]); // ── Unsaved changes guard ──────────────────────────────────── useEffect(() => { function handleBeforeUnload(e: BeforeUnloadEvent) { if (dirty) { e.preventDefault(); e.returnValue = ''; } } window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [dirty]); async function loadSettings() { setLoading(true); try { const res = await fetch('/api/admin/settings'); if (res.ok) { const { settings: data } = await res.json(); setSettings(data || {}); } } catch (err) { console.error('Failed to load settings:', err); } finally { setLoading(false); } } function update(path: string, value: unknown) { setSettings((s) => { const copy = JSON.parse(JSON.stringify(s)); const parts = path.split('.'); let obj = copy; for (let i = 0; i < parts.length - 1; i++) { if (!obj[parts[i]]) obj[parts[i]] = {}; obj = obj[parts[i]]; } const key = parts[parts.length - 1]; if (value === '' || value === undefined) { delete obj[key]; } else { obj[key] = value; } return copy; }); setDirty(true); setSaveMessage(''); } async function handleSave() { setSaving(true); setSaveMessage(''); // Clean up empty objects const cleaned = JSON.parse(JSON.stringify(settings)); for (const key of Object.keys(cleaned)) { if (typeof cleaned[key] === 'object' && Object.keys(cleaned[key]).length === 0) { delete cleaned[key]; } } try { const res = await fetch('/api/admin/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ settings: cleaned }), }); if (res.ok) { const data = await res.json(); setDirty(false); setSaveMessage(data.message || 'Saved!'); setTimeout(() => setSaveMessage(''), 5000); } else { const err = await res.json(); setSaveMessage(`Error: ${err.error}`); } } catch { setSaveMessage('Error: Failed to save'); } finally { setSaving(false); } } if (loading) { return (
); } const sections = [ { id: 'general', label: 'General' }, { id: 'theme', label: 'Theme' }, { id: 'grid', label: 'Grid' }, { id: 'footer', label: 'Footer' }, { id: 'legal', label: 'Legal' }, { id: 'seo', label: 'SEO' }, ]; return (
{/* Save Bar */}
{dirty && Unsaved changes} {saveMessage && ( {saveMessage} )}
{/* Sidebar */} {/* Content */}
{activeSection === 'general' && (

General

update('title', e.target.value)} placeholder="My Portfolio" />
update('subtitle', e.target.value)} placeholder="A visual journal" />
)} {activeSection === 'theme' && (

Theme

{PRESETS.map((p) => ( ))}
update('theme.accent', e.target.value)} /> update('theme.accent', e.target.value)} placeholder="#e60012" />
{PHOTO_FRAMES.map((f) => ( ))}
)} {activeSection === 'grid' && (

Grid Layout

{LAYOUTS.map((l) => ( ))}
update('grid.columns', parseInt(e.target.value) || 3)} />
update('grid.gap', parseInt(e.target.value) || 0)} />
{ASPECT_RATIOS.map((r) => ( ))}
)} {activeSection === 'footer' && (

Footer

update('footer.name', e.target.value)} placeholder="My Photography" />
update('footer.instagram', e.target.value)} placeholder="https://instagram.com/your-handle" />
update('footer.email', e.target.value)} placeholder="hello@example.com" />
update('footer.website', e.target.value)} placeholder="https://example.com" />
)} {activeSection === 'legal' && (

Legal / Impressum

{settings.legal?.enabled && ( <>
update('legal.name', e.target.value)} placeholder="Max Mustermann" />
update('legal.address', e.target.value)} placeholder="Musterstraße 1" />
update('legal.zipCity', e.target.value)} placeholder="12345 Berlin" />
update('legal.country', e.target.value)} placeholder="Germany" />
update('legal.email', e.target.value)} placeholder="legal@example.com" />
update('legal.phone', e.target.value)} placeholder="+49 123 456789" />