'use client' import { useState, useEffect, useRef, useCallback } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { Upload, Plus, Edit2, Trash2, Save, X, LogOut, Image as ImageIcon, Film, Music, FileText, Eye, Loader2, } from 'lucide-react' type Memory = { id: number title: string content: string created_at: string updated_at: string } type MediaItem = { id: number filename: string original_name: string | null type: 'photo' | 'video' | 'music' caption: string | null sort_order: number created_at: string } export default function AdminPage() { const [authed, setAuthed] = useState(null) const [password, setPassword] = useState('') const [loginError, setLoginError] = useState('') const [loginLoading, setLoginLoading] = useState(false) const [memories, setMemories] = useState([]) const [photos, setPhotos] = useState([]) const [videos, setVideos] = useState([]) const [music, setMusic] = useState([]) const [uploading, setUploading] = useState(false) const [uploadCaption, setUploadCaption] = useState('') const [uploadStatus, setUploadStatus] = useState('') const [dragOver, setDragOver] = useState(false) const [editingMemory, setEditingMemory] = useState(null) const [newMemory, setNewMemory] = useState({ title: '', content: '' }) const [showNewMemory, setShowNewMemory] = useState(false) const [savingMemory, setSavingMemory] = useState(false) const fileInputRef = useRef(null) const loadData = useCallback(async () => { const [memoriesRes, mediaRes] = await Promise.all([ fetch('/api/memories'), fetch('/api/media'), ]) const [memoriesData, mediaData] = await Promise.all([ memoriesRes.json(), mediaRes.json(), ]) setMemories(Array.isArray(memoriesData) ? memoriesData : []) const items: MediaItem[] = Array.isArray(mediaData) ? mediaData : [] setPhotos(items.filter((m) => m.type === 'photo')) setVideos(items.filter((m) => m.type === 'video')) setMusic(items.filter((m) => m.type === 'music')) }, []) useEffect(() => { fetch('/api/auth') .then((r) => r.json()) .then((d) => { setAuthed(d.authed) if (d.authed) loadData() }) .catch(() => setAuthed(false)) }, [loadData]) const login = async (e: React.FormEvent) => { e.preventDefault() setLoginLoading(true) setLoginError('') try { const res = await fetch('/api/auth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }), }) if (res.ok) { setAuthed(true) loadData() } else { setLoginError('Falsches Passwort') } } finally { setLoginLoading(false) } } const logout = async () => { await fetch('/api/auth', { method: 'DELETE' }) setAuthed(false) setPassword('') } const uploadFile = async (file: File) => { const formData = new FormData() formData.append('file', file) if (uploadCaption) formData.append('caption', uploadCaption) const res = await fetch('/api/upload', { method: 'POST', body: formData }) if (!res.ok) { const err = await res.json().catch(() => ({})) setUploadStatus(`Fehler: ${err.error || 'Unbekannter Fehler'}`) return false } return true } const handleFiles = async (files: File[]) => { if (files.length === 0) return setUploading(true) setUploadStatus(`Lade ${files.length} Datei(en) hoch...`) let success = 0 for (const file of files) { const ok = await uploadFile(file) if (ok) success++ } setUploadStatus(`${success} von ${files.length} hochgeladen.`) setUploadCaption('') await loadData() setUploading(false) if (fileInputRef.current) fileInputRef.current.value = '' setTimeout(() => setUploadStatus(''), 3000) } const handleFileChange = (e: React.ChangeEvent) => { handleFiles(Array.from(e.target.files || [])) } const handleDrop = (e: React.DragEvent) => { e.preventDefault() setDragOver(false) handleFiles(Array.from(e.dataTransfer.files)) } const deleteMedia = async (id: number) => { if (!confirm('Datei wirklich löschen?')) return await fetch(`/api/media/${id}`, { method: 'DELETE' }) loadData() } const saveMemory = async (e: React.FormEvent) => { e.preventDefault() setSavingMemory(true) try { if (editingMemory) { await fetch(`/api/memories/${editingMemory.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: editingMemory.title, content: editingMemory.content, }), }) setEditingMemory(null) } else { await fetch('/api/memories', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newMemory), }) setNewMemory({ title: '', content: '' }) setShowNewMemory(false) } loadData() } finally { setSavingMemory(false) } } const deleteMemory = async (id: number) => { if (!confirm('Erinnerung wirklich löschen?')) return await fetch(`/api/memories/${id}`, { method: 'DELETE' }) loadData() } // ── Loading state ────────────────────────────────────────────────────────── if (authed === null) { return (
) } // ── Login ───────────────────────────────────────────────────────────────── if (!authed) { return (

Verwaltung

Maria Malejka · Gedenkseite

setPassword(e.target.value)} placeholder="Passwort" className="w-full px-4 py-3 rounded-xl border border-warm-border bg-white text-warm-brown focus:outline-none focus:ring-2 focus:ring-amber-400 font-lora" autoFocus /> {loginError && (

{loginError}

)}
← Zur Gedenkseite
) } // ── Admin UI ─────────────────────────────────────────────────────────────── return (
{/* Header */}
Seite ansehen

Verwaltung

{/* ── Upload Section ───────────────────────────────────────────────── */}

Dateien hochladen

setUploadCaption(e.target.value)} placeholder="Bildunterschrift / Beschreibung (optional)" className="w-full px-4 py-2.5 rounded-xl border border-warm-border bg-white text-warm-brown text-sm focus:outline-none focus:ring-2 focus:ring-amber-400 font-lora" />
{ e.preventDefault(); setDragOver(true) }} onDragLeave={() => setDragOver(false)} className={`border-2 border-dashed rounded-2xl p-8 text-center transition-colors ${ dragOver ? 'border-amber-500 bg-amber-50/50' : 'border-warm-border hover:border-amber-400' }`} >

Fotos, Videos oder Musik hochladen

JPG, PNG, HEIC (iPhone), MP4, MOV, MP3, M4A — mehrere auf einmal möglich

{uploadStatus && (

{uploadStatus}

)}
{/* ── Photos ──────────────────────────────────────────────────────── */}

Fotos ({photos.length})

{photos.length === 0 ? (

Noch keine Fotos hochgeladen.

) : (
{photos.map((photo) => (
{photo.caption {photo.caption && (

{photo.caption}

)}
))}
)}
{/* ── Videos ──────────────────────────────────────────────────────── */}

Videos ({videos.length})

{videos.length === 0 ? (

Noch keine Videos hochgeladen.

) : (
{videos.map((video) => (
))}
)}
{/* ── Music ───────────────────────────────────────────────────────── */}

Musik ({music.length})

{music.length === 0 ? (

Noch keine Musik hochgeladen.

) : (
{music.map((track, i) => (
{i + 1}.

{track.original_name?.replace(/\.[^/.]+$/, '') || track.caption || track.filename}

))}
)}
{/* ── Memories ────────────────────────────────────────────────────── */}

Erinnerungen ({memories.length})

{/* New / Edit form */} {(showNewMemory || editingMemory) && (
{editingMemory ? 'Erinnerung bearbeiten' : 'Neue Erinnerung'}
editingMemory ? setEditingMemory({ ...editingMemory, title: e.target.value }) : setNewMemory({ ...newMemory, title: e.target.value }) } placeholder="Titel" required className="w-full px-4 py-2.5 rounded-xl border border-warm-border bg-white font-cormorant italic text-xl text-warm-brown focus:outline-none focus:ring-2 focus:ring-amber-400" />