import React, { useState, useEffect, FormEvent } from "react"; import { AuthorData, AuthorProfile, Book, Project } from "../types"; import { Lock, Save, Plus, Trash2, Edit2, Check, RefreshCw, Sparkles, BookOpen, Compass, Clipboard, Eye, Music, LogOut, ArrowLeftRight, Palette, Type, Image as ImageIcon, Globe, Tag } from "lucide-react"; import ImagePicker from "./ImagePicker"; interface AdminPanelProps { onLogout: () => void; } export default function AdminPanel({ onLogout }: AdminPanelProps) { const [password, setPassword] = useState(""); const [isLoggedIn, setIsLoggedIn] = useState(false); const [token, setToken] = useState(""); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); // Author Data State const [authorData, setAuthorData] = useState(null); // Selected Profile state ("scifi" vs "erotica" vs "clara" vs "renee") const [activeProfile, setActiveProfile] = useState<"scifi" | "erotica" | "clara" | "renee">("scifi"); // Sub-tab selection state ("profile" | "texts" | "design" | "books" | "projects") const [activeTab, setActiveTab] = useState<"profile" | "texts" | "design" | "books" | "projects">("profile"); // Editing forms state const [editingBookId, setEditingBookId] = useState(null); const [bookForm, setBookForm] = useState>({}); const [editingProjectId, setEditingProjectId] = useState(null); const [projectForm, setProjectForm] = useState>({}); // AI Prompt Tool State const [aiTitle, setAiTitle] = useState(""); const [aiGenre, setAiGenre] = useState(""); const [aiIdeas, setAiIdeas] = useState(""); const [aiTone, setAiTone] = useState("Geheimnisvoll"); const [aiLoading, setAiLoading] = useState(false); const [aiResult, setAiResult] = useState(""); // Check login state on component mount useEffect(() => { const storedToken = localStorage.getItem("author_admin_token"); if (storedToken) { setToken(storedToken); setIsLoggedIn(true); fetchAuthorData(); } }, []); const fetchAuthorData = async () => { setLoading(true); try { const res = await fetch("/api/author-data"); const data = await res.json(); setAuthorData(data); } catch (err) { console.error("Error fetching author data:", err); } finally { setLoading(false); } }; const handleLogin = async (e: FormEvent) => { e.preventDefault(); setError(""); setLoading(true); try { const res = await fetch("/api/admin/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password }), }); const data = await res.json(); if (res.ok && data.success) { localStorage.setItem("author_admin_token", data.token); setToken(data.token); setIsLoggedIn(true); fetchAuthorData(); } else { setError(data.error || "Ungültiges Passwort."); } } catch (err) { setError("Verbindung zum Server fehlgeschlagen."); } finally { setLoading(false); } }; const handleLogoutLocal = () => { localStorage.removeItem("author_admin_token"); setIsLoggedIn(false); setToken(""); onLogout(); }; // Profile save handler const handleSaveProfileInfo = async (profileKey: "scifi" | "erotica" | "clara" | "renee") => { if (!authorData) return; setError(""); setLoading(true); try { const res = await fetch("/api/admin/save-profile", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ profileKey, profileData: authorData[profileKey] }), }); const resData = await res.json(); if (res.ok && resData.success) { alert("Profilinformationen erfolgreich gespeichert!"); fetchAuthorData(); } else { setError(resData.error || "Profile konnte nicht gespeichert werden."); } } catch (err) { setError("Fehler beim Senden der Profildaten."); } finally { setLoading(false); } }; // Helper properties to access the selected Profile object const profile = authorData ? authorData[activeProfile] : null; // --- Profile state updaters --- const updateProfileField = (field: keyof AuthorProfile, value: any) => { if (!authorData || !profile) return; const updatedProfile = { ...profile, [field]: value }; setAuthorData({ ...authorData, [activeProfile]: updatedProfile }); }; // --- PROJECT ACTIONS --- const handleSaveProject = async () => { if (!authorData || !profile) return; let updatedProjects = [...profile.projects]; if (editingProjectId === "new") { const newProj: Project = { id: "proj_" + Date.now(), title: projectForm.title || "Unbenanntes Projekt", status: projectForm.status || "Konzept", progress: projectForm.progress || 0, description: projectForm.description || "" }; updatedProjects.push(newProj); } else { updatedProjects = updatedProjects.map(p => p.id === editingProjectId ? { ...p, ...projectForm } : p ); } const updatedProfile = { ...profile, projects: updatedProjects }; const nextData = { ...authorData, [activeProfile]: updatedProfile }; // Save to server setLoading(true); try { const res = await fetch("/api/admin/save-profile", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ profileKey: activeProfile, profileData: updatedProfile }), }); if (res.ok) { setAuthorData(nextData); setEditingProjectId(null); setProjectForm({}); } else { alert("Fehler beim Speichern des Projekts."); } } catch (err) { alert("Netzwerkfehler."); } finally { setLoading(false); } }; const handleDeleteProject = async (projId: string) => { if (!authorData || !profile || !confirm("Möchten Sie dieses Projekt wirklich löschen?")) return; const updatedProjects = profile.projects.filter(p => p.id !== projId); const updatedProfile = { ...profile, projects: updatedProjects }; const nextData = { ...authorData, [activeProfile]: updatedProfile }; setLoading(true); try { const res = await fetch("/api/admin/save-profile", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ profileKey: activeProfile, profileData: updatedProfile }), }); if (res.ok) { setAuthorData(nextData); } else { alert("Fehler beim Löschen."); } } catch (err) { alert("Netzwerkfehler."); } finally { setLoading(false); } }; // --- BOOK ACTIONS --- const handleSaveBook = async () => { if (!authorData || !profile) return; let updatedBooks = [...profile.books]; if (editingBookId === "new") { const newBook: Book = { id: "book_" + Date.now(), title: bookForm.title || "Unbenanntes Buch", subtitle: bookForm.subtitle || "", coverUrl: bookForm.coverUrl || "https://images.unsplash.com/photo-1543002588-bfa74002ed7e?auto=format&fit=crop&q=80&w=400", description: bookForm.description || "", publishedYear: bookForm.publishedYear || new Date().getFullYear(), buyLink: bookForm.buyLink || "", spotifyPlaylistId: bookForm.spotifyPlaylistId || "", genres: bookForm.genres || [] }; updatedBooks.push(newBook); } else { updatedBooks = updatedBooks.map(b => b.id === editingBookId ? { ...b, ...bookForm } as Book : b ); } const updatedProfile = { ...profile, books: updatedBooks }; const nextData = { ...authorData, [activeProfile]: updatedProfile }; setLoading(true); try { const res = await fetch("/api/admin/save-profile", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ profileKey: activeProfile, profileData: updatedProfile }), }); if (res.ok) { setAuthorData(nextData); setEditingBookId(null); setBookForm({}); } else { alert("Fehler beim Speichern des Buches."); } } catch (err) { alert("Netzwerkfehler."); } finally { setLoading(false); } }; const handleDeleteBook = async (bookId: string) => { if (!authorData || !profile || !confirm("Möchten Sie dieses Buch aus dem Bücherregal löschen?")) return; const updatedBooks = profile.books.filter(b => b.id !== bookId); const updatedProfile = { ...profile, books: updatedBooks }; const nextData = { ...authorData, [activeProfile]: updatedProfile }; setLoading(true); try { const res = await fetch("/api/admin/save-profile", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ profileKey: activeProfile, profileData: updatedProfile }), }); if (res.ok) { setAuthorData(nextData); } else { alert("Fehler beim Löschen des Buches."); } } catch (err) { alert("Netzwerkfehler."); } finally { setLoading(false); } }; // --- GEMINI SYNOPSIS GENERATOR --- const handleGenerateAISynopsis = async () => { if (!aiTitle.trim()) { alert("Bitte geben Sie einen Buchtitel für den KI-Assistenten an."); return; } setAiLoading(true); setAiResult(""); try { const res = await fetch("/api/admin/generate-blurb", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, body: JSON.stringify({ title: aiTitle, genre: aiGenre || activeProfile === "scifi" ? "Science-Fiction" : "Sinnliche Literatur", ideas: aiIdeas, tone: aiTone }), }); const data = await res.json(); if (res.ok && data.text) { setAiResult(data.text); setBookForm({ ...bookForm, description: data.text }); } else { alert(data.error || "Generierung fehlgeschlagen."); } } catch { alert("Fehler bei der Verbindung zur KI-Schnittstelle."); } finally { setAiLoading(false); } }; if (!isLoggedIn) { return (

Autoren-Zentrale Admin

Authentifizierung erforderlich. Passwortgeschütztes Kontrollzentrum.

setPassword(e.target.value)} placeholder="Passwort eingeben..." className="w-full px-4 py-3 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-cyan-400 transition-colors text-sm" required /> Standard-Passwort im Self-Host ist `autor2026`, anpassbar via Env-Variable `ADMIN_PASSWORD`.
{error && (
{error}
)}
); } if (!authorData || !profile) { return (
Lade Portfolio-Strukturen und Datenbank...
); } return (
{/* Top Navbar */} {/* Main Admin Workspace Container */}
{/* Left Side: Navigation Links & Theme Context info */}

Aktive Identität

{profile.name}

{profile.name}

{activeProfile === "scifi" ? "Realname" : "Pseudonym"}
{/* Sub navigation inside the active profile */}

KI-Kopierassistent

Die 21. Jahrhundert Schreibzentrale. Generieren oder überarbeiten Sie Ihre Klappentexte für das Bücherregal mit modernster Gemini AI.

{/* Right Side: Active Workspace Editor Content */}
{error && (
{error}
)} {/* TAB 1: Profile Main Information Editor */} {activeTab === "profile" && (

Profil & Biografie verwalten

Bearbeiten Sie den öffentlichen Steckbrief für {profile.name}.

updateProfileField("name", e.target.value)} className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm" />
updateProfileField("avatarUrl", url)} token={token} label="Autorenfoto / Avatar" />
updateProfileField("heroTitle", e.target.value)} className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm" />
updateProfileField("heroSubtitle", e.target.value)} className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm" />