webstack-author/src/components/AdminPanel.tsx
2026-08-15 09:49:03 +02:00

2167 lines
116 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, FormEvent } from "react";
import Markdown from "react-markdown";
import { AuthorData, AuthorProfile, Book, Project, LegalDocument } from "../types";
import {
Save, Plus, Trash2, Edit2, Check, RefreshCw, Sparkles,
BookOpen, Compass, Clipboard, Eye, Music, LogOut, ArrowLeftRight,
Palette, Type, Image as ImageIcon, Globe, Tag,
Scale, ShieldCheck, FileText, CheckSquare, Square, Copy, ArrowRight
} from "lucide-react";
import ImagePicker from "./ImagePicker";
import AdminLogin from "./admin/AdminLogin";
import { adminApi, AdminApiError } from "../api/adminApi";
interface AdminPanelProps {
onLogout: () => void;
}
export default function AdminPanel({ onLogout }: AdminPanelProps) {
const [password, setPassword] = useState("");
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [notice, setNotice] = useState<{ message: string; tone: "success" | "error" } | null>(null);
// Author Data State
const [authorData, setAuthorData] = useState<AuthorData | null>(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" | "legal")
const [activeTab, setActiveTab] = useState<"profile" | "texts" | "design" | "books" | "projects" | "legal">("profile");
// Editing forms state
const [editingBookId, setEditingBookId] = useState<string | null>(null);
const [bookForm, setBookForm] = useState<Partial<Book>>({});
const [genreInputRaw, setGenreInputRaw] = useState("");
const [editingProjectId, setEditingProjectId] = useState<string | null>(null);
const [projectForm, setProjectForm] = useState<Partial<Project>>({});
// Legal Documents state
const [editingLegalId, setEditingLegalId] = useState<string | null>(null);
const [legalForm, setLegalForm] = useState<Partial<LegalDocument>>({});
const [legalPreviewMode, setLegalPreviewMode] = useState<"edit" | "preview">("edit");
// 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("");
// Restore a valid server-side session on component mount.
useEffect(() => {
// Remove the legacy value that used to contain the admin password.
localStorage.removeItem("author_admin_token");
const restoreSession = async () => {
setLoading(true);
try {
await adminApi.session();
setIsLoggedIn(true);
await fetchAuthorData();
} catch (err) {
console.error("Admin session check failed:", err);
} finally {
setLoading(false);
}
};
restoreSession();
}, []);
useEffect(() => {
const handleExpiredSession = () => {
setIsLoggedIn(false);
setAuthorData(null);
setError("Die Admin-Sitzung ist abgelaufen. Bitte erneut anmelden.");
};
window.addEventListener("admin-session-expired", handleExpiredSession);
return () => window.removeEventListener("admin-session-expired", handleExpiredSession);
}, []);
const fetchAuthorData = async () => {
setLoading(true);
try {
const data = await adminApi.loadData();
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 {
await adminApi.login(password);
setPassword("");
setIsLoggedIn(true);
await fetchAuthorData();
} catch (err) {
setError(err instanceof AdminApiError ? err.message : "Verbindung zum Server fehlgeschlagen.");
} finally {
setLoading(false);
}
};
const handleLogoutLocal = async () => {
try {
await adminApi.logout();
} catch (err) {
console.error("Admin logout failed:", err);
}
setIsLoggedIn(false);
setAuthorData(null);
onLogout();
};
const persistProfile = async (profileKey: "scifi" | "erotica" | "clara" | "renee", profileData: AuthorProfile) => {
if (!authorData) throw new Error("Keine Profildaten geladen.");
const result = await adminApi.saveProfile(profileKey, profileData, authorData.revision ?? 0);
setAuthorData((current) => current ? { ...current, [profileKey]: profileData, revision: result.revision } : current);
return result;
};
const persistLegalDocuments = async (legalDocuments: LegalDocument[]) => {
if (!authorData) throw new Error("Keine Profildaten geladen.");
const result = await adminApi.saveLegal(legalDocuments, authorData.revision ?? 0);
setAuthorData((current) => current ? { ...current, legalDocuments, revision: result.revision } : current);
return result;
};
const showNotice = (message: string, tone: "success" | "error" = "error") => setNotice({ message, tone });
useEffect(() => {
if (!notice) return;
const timeout = window.setTimeout(() => setNotice(null), 5000);
return () => window.clearTimeout(timeout);
}, [notice]);
// Profile save handler
const handleSaveProfileInfo = async (profileKey: "scifi" | "erotica" | "clara" | "renee") => {
if (!authorData) return;
setError("");
setLoading(true);
try {
await persistProfile(profileKey, authorData[profileKey]);
showNotice("Profilinformationen erfolgreich gespeichert!", "success");
} catch (err) {
setError(err instanceof AdminApiError ? err.message : "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
});
};
const updateCustomSectionLink = (index: number, field: "label" | "url", value: string) => {
if (!profile) return;
const links = Array.from({ length: 3 }, (_, linkIndex) => ({
label: profile.customSectionLinks?.[linkIndex]?.label || "",
url: profile.customSectionLinks?.[linkIndex]?.url || "",
}));
links[index] = { ...links[index], [field]: value };
updateProfileField("customSectionLinks", links);
};
// --- 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 || "",
imageUrl: projectForm.imageUrl || "",
spotifyPlaylistId: projectForm.spotifyPlaylistId || ""
};
updatedProjects.push(newProj);
} else {
updatedProjects = updatedProjects.map(p =>
p.id === editingProjectId ? { ...p, ...projectForm } : p
);
}
const updatedProfile = { ...profile, projects: updatedProjects };
// Save to server
setLoading(true);
try {
await persistProfile(activeProfile, updatedProfile);
setEditingProjectId(null);
setProjectForm({});
} catch (err) {
showNotice(err instanceof AdminApiError ? err.message : "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 };
setLoading(true);
try {
await persistProfile(activeProfile, updatedProfile);
} catch (err) {
showNotice(err instanceof AdminApiError ? err.message : "Netzwerkfehler.");
} finally {
setLoading(false);
}
};
// --- BOOK ACTIONS ---
const handleSaveBook = async () => {
if (!authorData || !profile) return;
const parsedGenres = genreInputRaw.split(",").map(g => g.trim()).filter(Boolean);
let updatedBooks = [...profile.books];
// If current book is marked as spotlight, clear spotlight flag on all other books
if (bookForm.isSpotlight) {
updatedBooks = updatedBooks.map(b => ({ ...b, isSpotlight: false }));
}
if (editingBookId === "new") {
const newBook: Book = {
id: "book_" + Date.now(),
title: bookForm.title || "Unbenanntes Buch",
subtitle: bookForm.subtitle || "",
coverUrl: bookForm.coverUrl || "",
description: bookForm.description || "",
publishedYear: bookForm.publishedYear || new Date().getFullYear(),
buyLink: bookForm.buyLink || "",
spotifyPlaylistId: bookForm.spotifyPlaylistId || "",
genres: parsedGenres,
isSpotlight: !!bookForm.isSpotlight,
spotlightBadge: bookForm.spotlightBadge || ""
};
updatedBooks.push(newBook);
} else {
updatedBooks = updatedBooks.map(b =>
b.id === editingBookId ? {
...b,
...bookForm,
genres: parsedGenres,
isSpotlight: !!bookForm.isSpotlight,
spotlightBadge: bookForm.spotlightBadge || ""
} as Book : b
);
}
const updatedProfile = { ...profile, books: updatedBooks };
setLoading(true);
try {
await persistProfile(activeProfile, updatedProfile);
setEditingBookId(null);
setBookForm({});
} catch (err) {
showNotice(err instanceof AdminApiError ? err.message : "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 };
setLoading(true);
try {
await persistProfile(activeProfile, updatedProfile);
} catch (err) {
showNotice(err instanceof AdminApiError ? err.message : "Netzwerkfehler.");
} finally {
setLoading(false);
}
};
// --- GEMINI SYNOPSIS GENERATOR ---
const handleGenerateAISynopsis = async () => {
if (!aiTitle.trim()) {
showNotice("Bitte geben Sie einen Buchtitel für den KI-Assistenten an.");
return;
}
setAiLoading(true);
setAiResult("");
try {
const data = await adminApi.generateBlurb({
title: aiTitle,
genre: aiGenre || (activeProfile === "scifi" ? "Science-Fiction" : "Literatur"),
ideas: aiIdeas,
tone: aiTone,
});
setAiResult(data.text);
setBookForm({ ...bookForm, description: data.text });
} catch (err) {
showNotice(err instanceof AdminApiError ? err.message : "Fehler bei der Verbindung zur KI-Schnittstelle.");
} finally {
setAiLoading(false);
}
};
// --- LEGAL DOCUMENTS MANAGEMENT ---
const handleSaveLegalDocument = async () => {
if (!authorData) return;
const currentDocs = authorData.legalDocuments || [];
let updatedDocs = [...currentDocs];
const assigned: ("scifi" | "erotica" | "clara" | "renee")[] =
legalForm.assignedProfiles && legalForm.assignedProfiles.length > 0
? legalForm.assignedProfiles
: ["scifi", "erotica", "clara", "renee"];
if (editingLegalId === "new") {
const newDoc: LegalDocument = {
id: "legal_" + Date.now(),
title: legalForm.title || "Rechtliches Dokument",
type: legalForm.type || "impressum",
content: legalForm.content || "",
updatedAt: legalForm.updatedAt || new Date().toISOString().split("T")[0],
assignedProfiles: assigned
};
updatedDocs.push(newDoc);
} else {
updatedDocs = updatedDocs.map(d =>
d.id === editingLegalId
? {
...d,
...legalForm,
assignedProfiles: assigned,
updatedAt: legalForm.updatedAt || new Date().toISOString().split("T")[0]
} as LegalDocument
: d
);
}
setLoading(true);
try {
await persistLegalDocuments(updatedDocs);
setEditingLegalId(null);
setLegalForm({});
} catch (err) {
showNotice(err instanceof AdminApiError ? err.message : "Netzwerkfehler beim Speichern.");
} finally {
setLoading(false);
}
};
const handleDeleteLegalDocument = async (docId: string) => {
if (!authorData || !confirm("Möchten Sie dieses rechtliche Dokument wirklich löschen?")) return;
const updatedDocs = (authorData.legalDocuments || []).filter(d => d.id !== docId);
setLoading(true);
try {
await persistLegalDocuments(updatedDocs);
if (editingLegalId === docId) {
setEditingLegalId(null);
setLegalForm({});
}
} catch (err) {
showNotice(err instanceof AdminApiError ? err.message : "Netzwerkfehler beim Löschen.");
} finally {
setLoading(false);
}
};
const handleDuplicateLegalDocument = (doc: LegalDocument) => {
setEditingLegalId("new");
setLegalForm({
title: `${doc.title} (Kopie)`,
type: doc.type,
content: doc.content,
updatedAt: new Date().toISOString().split("T")[0],
assignedProfiles: [...doc.assignedProfiles]
});
};
const toggleProfileAssignment = (pKey: "scifi" | "erotica" | "clara" | "renee") => {
const current = legalForm.assignedProfiles || [];
if (current.includes(pKey)) {
setLegalForm({ ...legalForm, assignedProfiles: current.filter(k => k !== pKey) });
} else {
setLegalForm({ ...legalForm, assignedProfiles: [...current, pKey] });
}
};
const handleAssignAllProfiles = (assignAll: boolean) => {
if (assignAll) {
setLegalForm({ ...legalForm, assignedProfiles: ["scifi", "erotica", "clara", "renee"] });
} else {
setLegalForm({ ...legalForm, assignedProfiles: [] });
}
};
if (!isLoggedIn) {
return <AdminLogin password={password} error={error} loading={loading} onPasswordChange={setPassword} onSubmit={handleLogin} />;
}
if (!authorData || !profile) {
return (
<div className="min-h-screen bg-slate-950 text-slate-100 flex items-center justify-center text-sm font-mono">
<RefreshCw className="w-6 h-6 animate-spin text-cyan-400 mr-2" />
Lade Portfolio-Strukturen und Datenbank...
</div>
);
}
return (
<div className="min-h-screen bg-slate-950 text-slate-200">
{notice && (
<div
role="status"
aria-live="polite"
className={`fixed right-4 top-4 z-[70] max-w-sm rounded-xl border px-4 py-3 text-sm shadow-2xl ${notice.tone === "success" ? "border-emerald-700 bg-emerald-950 text-emerald-100" : "border-rose-800 bg-rose-950 text-rose-100"}`}
>
{notice.message}
</div>
)}
{/* Top Navbar */}
<nav className="bg-slate-900/80 border-b border-slate-800 sticky top-0 z-30 backdrop-blur-md">
<div className="max-w-7xl mx-auto px-4 py-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-3">
<span className="p-2 bg-indigo-950 border border-indigo-700/50 rounded-lg text-cyan-400 font-mono text-sm">A</span>
<div>
<h1 className="text-lg font-bold text-white flex items-center gap-2">
<span>Autoren-Administration</span>
</h1>
<p className="text-[10px] text-slate-400 font-mono uppercase tracking-wider">Dual Identity System v1.5</p>
</div>
</div>
<div className="flex items-center gap-3">
{/* Swapping genre profile profile */}
<div className="bg-slate-950 p-0.5 rounded-xl border border-slate-800 flex flex-wrap gap-0.5">
<button
onClick={() => { setActiveProfile("scifi"); setActiveTab("profile"); }}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold cursor-pointer transition-all ${activeProfile === "scifi" ? "bg-cyan-500 text-slate-950" : "text-slate-400 hover:text-white"}`}
>
Sci-Fi ({authorData.scifi.name})
</button>
<button
onClick={() => { setActiveProfile("erotica"); setActiveTab("profile"); }}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold cursor-pointer transition-all ${activeProfile === "erotica" ? "bg-rose-800 text-white" : "text-slate-400 hover:text-white"}`}
>
Erotik ({authorData.erotica.name})
</button>
<button
onClick={() => { setActiveProfile("clara"); setActiveTab("profile"); }}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold cursor-pointer transition-all ${activeProfile === "clara" ? "bg-emerald-700 text-white" : "text-slate-400 hover:text-white"}`}
>
Naturlyrik ({authorData.clara.name})
</button>
<button
onClick={() => { setActiveProfile("renee"); setActiveTab("profile"); }}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold cursor-pointer transition-all ${activeProfile === "renee" ? "bg-yellow-500 text-slate-950" : "text-slate-400 hover:text-white"}`}
>
Romantik ({authorData.renee.name})
</button>
</div>
<button
onClick={handleLogoutLocal}
className="px-3 py-1.5 bg-slate-800 hover:bg-rose-950 border border-slate-700 hover:border-rose-900 rounded-lg text-xs font-mono text-slate-300 hover:text-white transition-all flex items-center gap-1.5 cursor-pointer"
>
<LogOut className="w-3.5 h-3.5" />
<span>Abmelden</span>
</button>
</div>
</div>
</nav>
{/* Main Admin Workspace Container */}
<div className="max-w-7xl mx-auto px-4 py-8 grid grid-cols-1 lg:grid-cols-12 gap-8">
{/* Left Side: Navigation Links & Theme Context info */}
<div className="lg:col-span-3 space-y-4">
<div className="p-4 rounded-2xl bg-slate-900 border border-slate-800 space-y-4">
<h3 className="text-xs font-mono text-indigo-400 uppercase tracking-wider">Aktive Identität</h3>
<div className="flex items-center gap-3">
<img
src={profile.avatarUrl || undefined}
alt={profile.name}
className="w-10 h-10 rounded-full object-cover border border-slate-700"
referrerPolicy="no-referrer"
/>
<div>
<h4 className="text-sm font-bold text-white">{profile.name}</h4>
<span className="text-[10px] text-slate-500 uppercase tracking-widest">{activeProfile === "scifi" ? "Realname" : "Pseudonym"}</span>
</div>
</div>
{/* Sub navigation inside the active profile */}
<div className="flex flex-col gap-1 pt-2">
<button
onClick={() => setActiveTab("profile")}
className={`px-3 py-2.5 rounded-xl text-left text-xs font-bold transition-all flex items-center gap-2 cursor-pointer ${activeTab === "profile" ? "bg-slate-800 border-l-4 border-indigo-500 text-white" : "text-slate-400 hover:bg-slate-800/40 hover:text-white"}`}
>
<Compass className="w-4 h-4" />
<span>Profil & Biografie info</span>
</button>
<button
onClick={() => setActiveTab("texts")}
className={`px-3 py-2.5 rounded-xl text-left text-xs font-bold transition-all flex items-center gap-2 cursor-pointer ${activeTab === "texts" ? "bg-slate-800 border-l-4 border-indigo-500 text-white" : "text-slate-400 hover:bg-slate-800/40 hover:text-white"}`}
>
<Type className="w-4 h-4" />
<span>Texte & Überschriften</span>
</button>
<button
onClick={() => setActiveTab("design")}
className={`px-3 py-2.5 rounded-xl text-left text-xs font-bold transition-all flex items-center gap-2 cursor-pointer ${activeTab === "design" ? "bg-slate-800 border-l-4 border-indigo-500 text-white" : "text-slate-400 hover:bg-slate-800/40 hover:text-white"}`}
>
<Palette className="w-4 h-4" />
<span>Design & Farben</span>
</button>
<button
onClick={() => setActiveTab("projects")}
className={`px-3 py-2.5 rounded-xl text-left text-xs font-bold transition-all flex items-center gap-2 cursor-pointer ${activeTab === "projects" ? "bg-slate-800 border-l-4 border-indigo-500 text-white" : "text-slate-400 hover:bg-slate-800/40 hover:text-white"}`}
>
<Clipboard className="w-4 h-4" />
<span>Aktuelle Projekte ({profile.projects.length})</span>
</button>
<button
onClick={() => setActiveTab("books")}
className={`px-3 py-2.5 rounded-xl text-left text-xs font-bold transition-all flex items-center gap-2 cursor-pointer ${activeTab === "books" ? "bg-slate-800 border-l-4 border-indigo-500 text-white" : "text-slate-400 hover:bg-slate-800/40 hover:text-white"}`}
>
<BookOpen className="w-4 h-4" />
<span>Bücherregal ({profile.books.length})</span>
</button>
<button
onClick={() => { setActiveTab("legal"); setEditingLegalId(null); setLegalForm({}); }}
className={`px-3 py-2.5 rounded-xl text-left text-xs font-bold transition-all flex items-center gap-2 cursor-pointer ${activeTab === "legal" ? "bg-slate-800 border-l-4 border-indigo-500 text-white" : "text-slate-400 hover:bg-slate-800/40 hover:text-white"}`}
>
<Scale className="w-4 h-4" />
<span>Rechtliches & Impressum ({(authorData.legalDocuments || []).length})</span>
</button>
</div>
</div>
<div className="p-4 rounded-2xl bg-gradient-to-br from-indigo-950/20 to-slate-900 border border-slate-800/80 space-y-3">
<h4 className="text-xs font-mono text-cyan-400 uppercase tracking-wider flex items-center gap-1.5">
<Sparkles className="w-4 h-4 text-cyan-400" />
<span>KI-Kopierassistent</span>
</h4>
<p className="text-[11px] text-slate-300 leading-relaxed">
Die 21. Jahrhundert Schreibzentrale. Generieren oder überarbeiten Sie Ihre Klappentexte für das Bücherregal mit modernster Gemini AI.
</p>
</div>
</div>
{/* Right Side: Active Workspace Editor Content */}
<div className="lg:col-span-9 space-y-6">
{error && (
<div className="p-3 bg-rose-950/40 border border-rose-900/50 text-rose-300 text-xs rounded-xl text-center">
{error}
</div>
)}
{/* TAB 1: Profile Main Information Editor */}
{activeTab === "profile" && (
<div className="p-6 bg-slate-900 border border-slate-800 rounded-2xl space-y-6">
<div className="flex items-center justify-between border-b border-slate-800 pb-4">
<div>
<h2 className="text-lg font-bold text-white">Profil & Biografie verwalten</h2>
<p className="text-xs text-slate-400">Bearbeiten Sie den öffentlichen Steckbrief für {profile.name}.</p>
</div>
<button
onClick={() => handleSaveProfileInfo(activeProfile)}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer"
>
<Save className="w-3.5 h-3.5" />
<span>Profil speichern</span>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Öffentlicher Name</label>
<input
type="text"
value={profile.name}
onChange={(e) => 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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase font-bold text-white">Avatar / Autorenfoto</label>
<ImagePicker
value={profile.avatarUrl}
onChange={(url) => updateProfileField("avatarUrl", url)}
label="Autorenfoto / Avatar"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Hero Titel (Hauptüberschrift)</label>
<input
type="text"
value={profile.heroTitle}
onChange={(e) => 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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Hero Untertitel</label>
<input
type="text"
value={profile.heroSubtitle}
onChange={(e) => 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"
/>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-xs font-mono text-slate-400 uppercase">Über Mich / Biografie</label>
<textarea
rows={8}
value={profile.bio}
onChange={(e) => updateProfileField("bio", 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 font-sans"
/>
</div>
{/* Dynamic Schlagworte / Highlights */}
<div className="md:col-span-2 border-t border-slate-800/80 pt-6 mt-2 space-y-4">
<div>
<h4 className="text-sm font-bold text-white flex items-center gap-2">
<Tag className="w-4 h-4 text-amber-400" />
<span>Schlagworte & Fokus (3 Infoboxen unter "Über mich")</span>
</h4>
<p className="text-xs text-slate-500">Diese drei Kacheln erscheinen unter der Biografie. Sie können die Kategoriename (z.B. GENRE, TONALITÄT, FOKUS) und den zugehörigen Inhalt frei anpassen.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Schlagwort 1 */}
<div className="p-3 bg-slate-950 border border-slate-800 rounded-xl space-y-2">
<div className="text-xs font-semibold text-amber-400 uppercase flex items-center gap-1">
<span>Schlagwort 1</span>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Kategorie-Titel</label>
<input
type="text"
value={profile.bioTag1Label || ""}
onChange={(e) => updateProfileField("bioTag1Label", e.target.value)}
placeholder="z.B. GENRE"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs"
/>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Inhalt / Wert</label>
<input
type="text"
value={profile.bioTag1Value || ""}
onChange={(e) => updateProfileField("bioTag1Value", e.target.value)}
placeholder="z.B. Emotionale Romance"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs font-medium"
/>
</div>
</div>
{/* Schlagwort 2 */}
<div className="p-3 bg-slate-950 border border-slate-800 rounded-xl space-y-2">
<div className="text-xs font-semibold text-amber-400 uppercase flex items-center gap-1">
<span>Schlagwort 2</span>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Kategorie-Titel</label>
<input
type="text"
value={profile.bioTag2Label || ""}
onChange={(e) => updateProfileField("bioTag2Label", e.target.value)}
placeholder="z.B. TONALITÄT"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs"
/>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Inhalt / Wert</label>
<input
type="text"
value={profile.bioTag2Value || ""}
onChange={(e) => updateProfileField("bioTag2Value", e.target.value)}
placeholder="z.B. Herzlich & Hoffnungsvoll"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs font-medium"
/>
</div>
</div>
{/* Schlagwort 3 */}
<div className="p-3 bg-slate-950 border border-slate-800 rounded-xl space-y-2">
<div className="text-xs font-semibold text-amber-400 uppercase flex items-center gap-1">
<span>Schlagwort 3</span>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Kategorie-Titel</label>
<input
type="text"
value={profile.bioTag3Label || ""}
onChange={(e) => updateProfileField("bioTag3Label", e.target.value)}
placeholder="z.B. FOKUS"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs"
/>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Inhalt / Wert</label>
<input
type="text"
value={profile.bioTag3Value || ""}
onChange={(e) => updateProfileField("bioTag3Value", e.target.value)}
placeholder="z.B. Gefühl & Tiefe"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs font-medium"
/>
</div>
</div>
</div>
</div>
{/* Custom Domain & Routing Section */}
<div className="md:col-span-2 border-t border-slate-800/80 pt-6 mt-2 space-y-4">
<div>
<h4 className="text-sm font-bold text-white flex items-center gap-2">
<Globe className="w-4 h-4 text-indigo-400" />
<span>Domain- & Routing-Einstellungen</span>
</h4>
<p className="text-xs text-slate-500">Legen Sie fest, unter welchen Domains und Pfaden dieses Profil geladen werden soll. Sobald Besucher eine dieser Domains aufrufen, zeigt die Applikation automatisch diese Autoren-Suite an.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Eigene Domains (Domain-Routing)</label>
<input
type="text"
value={profile.customDomain || ""}
onChange={(e) => updateProfileField("customDomain", e.target.value)}
placeholder="z.B. autorin.de, www.autorin.de"
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 font-mono"
/>
<p className="text-[11px] text-slate-500">Kommagetrennte Domainnamen. Bsp: <code className="text-slate-400">autorin.de, autorin.com</code></p>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Eigener Pfad-Prefix (Pfad-Routing)</label>
<input
type="text"
value={profile.customPath || ""}
onChange={(e) => updateProfileField("customPath", e.target.value)}
placeholder="z.B. /renee-heart, /renee"
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 font-mono"
/>
<p className="text-[11px] text-slate-500">Kommagetrennte Pfade für Direktlinks & Vorschau. Bsp: <code className="text-slate-400">/renee-heart, /renee</code></p>
</div>
</div>
</div>
{/* Custom Highlight Section */}
<div className="md:col-span-2 border-t border-slate-800/80 pt-6 mt-2 space-y-4">
<div>
<h4 className="text-sm font-bold text-white flex items-center gap-2">
<Sparkles className="w-4 h-4 text-cyan-400" />
<span>Zusätzlicher Textabschnitt (unten auf der Seite)</span>
</h4>
<p className="text-xs text-slate-500">Ein frei gestaltbarer Infobereich, der am Ende des Portfolios (nach dem Bücherregal) dargestellt wird. Ideal für Termine, Lesungen, Newsletter-Aufrufe oder persönliche Grüße. Leer lassen, um den Bereich auszublenden.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-1 md:col-span-2">
<label className="text-xs font-mono text-slate-400 uppercase">Abschnitts-Titel</label>
<input
type="text"
value={profile.customSectionTitle || ""}
onChange={(e) => updateProfileField("customSectionTitle", e.target.value)}
placeholder="z.B. Lesungen, Kontakt oder Newsletter"
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"
/>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-xs font-mono text-slate-400 uppercase">Abschnitts-Inhalt (Markdown & Links unterstützt)</label>
<textarea
rows={4}
value={profile.customSectionContent || ""}
onChange={(e) => updateProfileField("customSectionContent", e.target.value)}
placeholder="Ihr individueller Textabschnitt..."
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 font-sans"
/>
</div>
<div className="space-y-3 md:col-span-2 pt-2">
<div>
<label className="text-xs font-mono text-slate-400 uppercase">Optionale Buttons (maximal 3)</label>
<p className="text-[11px] text-slate-500 mt-1">Ein Button erscheint nur, wenn Beschriftung und eine vollständige HTTP(S)-Adresse eingetragen sind. Damit freigegebene Ziele werden öffentlich verlinkt.</p>
</div>
{[0, 1, 2].map((index) => (
<div key={index} className="grid grid-cols-1 md:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)] gap-3 rounded-xl border border-slate-800 bg-slate-950/50 p-3">
<div className="space-y-1">
<label className="text-[10px] font-mono text-slate-500 uppercase">Button {index + 1} – Beschriftung</label>
<input
type="text"
maxLength={80}
value={profile.customSectionLinks?.[index]?.label || ""}
onChange={(event) => updateCustomSectionLink(index, "label", event.target.value)}
placeholder="z.B. Zur anderen Autorin"
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-lg text-white outline-none focus:border-indigo-500 text-sm"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-mono text-slate-500 uppercase">Ziel-URL</label>
<input
type="url"
value={profile.customSectionLinks?.[index]?.url || ""}
onChange={(event) => updateCustomSectionLink(index, "url", event.target.value)}
placeholder="https://www.beispiel.de"
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-lg text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
)}
{/* TAB 2: Static Texts & Headings */}
{activeTab === "texts" && (
<div className="p-6 bg-slate-900 border border-slate-800 rounded-2xl space-y-6">
<div className="flex items-center justify-between border-b border-slate-800 pb-4">
<div>
<h2 className="text-lg font-bold text-white flex items-center gap-2">
<Type className="w-5 h-5 text-indigo-400" />
<span>Statische Texte & Sektionsüberschriften</span>
</h2>
<p className="text-xs text-slate-400">Passen Sie alle Beschriftungen, Sektions-Header und Fußzeilen für {profile.name} an.</p>
</div>
<button
onClick={() => handleSaveProfileInfo(activeProfile)}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer"
>
<Save className="w-3.5 h-3.5" />
<span>Texte speichern</span>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Header Badge-Text (kleiner Tag oben)</label>
<input
type="text"
value={profile.badgeText || ""}
onChange={(e) => updateProfileField("badgeText", e.target.value)}
placeholder="z.B. Cyber-Mystisches Archive"
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"
/>
<p className="text-[10px] text-slate-500">Wird über dem Haupttitel mit feinem Glanz-Effekt angezeigt.</p>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase font-bold">"Über mich" Sektionsüberschrift</label>
<input
type="text"
value={profile.aboutTitle || ""}
onChange={(e) => updateProfileField("aboutTitle", e.target.value)}
placeholder="z.B. Über den Autor"
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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase font-bold text-amber-400 flex items-center gap-1">
<Sparkles className="w-3.5 h-3.5" />
<span>Spotlight / Neuerscheinung Sektionsüberschrift</span>
</label>
<input
type="text"
value={profile.spotlightTitle || ""}
onChange={(e) => updateProfileField("spotlightTitle", e.target.value)}
placeholder="z.B. Spotlight & Neuerscheinung"
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-amber-500 text-sm"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase text-amber-400">Spotlight Untertitel / Beschreibung</label>
<input
type="text"
value={profile.spotlightSubtitle || ""}
onChange={(e) => updateProfileField("spotlightSubtitle", e.target.value)}
placeholder="z.B. Besonders hervorgehobenes Werk im Fokus"
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-amber-500 text-sm"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Projekte Sektionsüberschrift</label>
<input
type="text"
value={profile.projectsTitle || ""}
onChange={(e) => updateProfileField("projectsTitle", e.target.value)}
placeholder="z.B. Aktuelle Projekte"
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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Projekte Untertitel / Beschreibung</label>
<input
type="text"
value={profile.projectsSubtitle || ""}
onChange={(e) => updateProfileField("projectsSubtitle", e.target.value)}
placeholder="z.B. Ein exklusiver Blick auf mein Schreib-Amboss..."
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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Projekt-Detail Exzerpt-Überschrift (im Modal)</label>
<input
type="text"
value={profile.projectExcerptTitle || ""}
onChange={(e) => updateProfileField("projectExcerptTitle", e.target.value)}
placeholder="z.B. Projekt-Exzerpt & Fortschrittsnotizen"
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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Projekt-Detail Badge / Wasserzeichen (z.B. SEC_REPORT_LOG)</label>
<input
type="text"
value={profile.projectExcerptBadge || ""}
onChange={(e) => updateProfileField("projectExcerptBadge", e.target.value)}
placeholder="z.B. SEC_REPORT_LOG, FRAGMENT_LOG_01..."
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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Projekt-Fortschritts-Label (z.B. Organischer Fortschritt)</label>
<input
type="text"
value={profile.projectProgressLabel || ""}
onChange={(e) => updateProfileField("projectProgressLabel", e.target.value)}
placeholder="z.B. Organischer Fortschritt, Reifegrad..."
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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Bücherregal Sektionsüberschrift</label>
<input
type="text"
value={profile.booksTitle || ""}
onChange={(e) => updateProfileField("booksTitle", e.target.value)}
placeholder="z.B. Bücherregal"
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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Bücherregal Untertitel / Beschreibung</label>
<input
type="text"
value={profile.booksSubtitle || ""}
onChange={(e) => updateProfileField("booksSubtitle", e.target.value)}
placeholder="z.B. Entdecken Sie meine verlegten Werke..."
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"
/>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-xs font-mono text-slate-400 uppercase">Footer-Text (Fußzeile ganz unten)</label>
<input
type="text"
value={profile.footerText || ""}
onChange={(e) => updateProfileField("footerText", e.target.value)}
placeholder="z.B. Alle Rechte vorbehalten..."
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"
/>
</div>
</div>
</div>
)}
{/* TAB 3: Design & Dynamic Theme Controls */}
{activeTab === "design" && (
<div className="p-6 bg-slate-900 border border-slate-800 rounded-2xl space-y-6">
<div className="flex items-center justify-between border-b border-slate-800 pb-4">
<div>
<h2 className="text-lg font-bold text-white flex items-center gap-2">
<Palette className="w-5 h-5 text-indigo-400" />
<span>Design, Farben & Themes</span>
</h2>
<p className="text-xs text-slate-400">Gestalten Sie das Erscheinungsbild für {profile.name} nach Ihren Wünschen.</p>
</div>
<button
onClick={() => handleSaveProfileInfo(activeProfile)}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer"
>
<Save className="w-3.5 h-3.5" />
<span>Design speichern</span>
</button>
</div>
{/* Preset Theme Selection */}
<div className="space-y-3">
<label className="text-xs font-mono text-cyan-400 uppercase tracking-widest font-bold">Schnell-Paletten (Presets)</label>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<button
type="button"
onClick={() => {
updateProfileField("accentColor", "#06b6d4");
updateProfileField("secondaryColor", "#a855f7");
updateProfileField("backgroundColor", "#020617");
updateProfileField("cardBgColor", "#0f172a");
updateProfileField("fontFamily", "sans");
}}
className="p-3 bg-slate-950 border border-cyan-500/40 hover:border-cyan-400 rounded-xl text-left space-y-2 cursor-pointer transition-all"
>
<div className="flex gap-1.5">
<div className="w-4 h-4 rounded-full bg-[#06b6d4]" />
<div className="w-4 h-4 rounded-full bg-[#a855f7]" />
<div className="w-4 h-4 rounded-full bg-[#020617] border border-slate-700" />
</div>
<div className="text-xs font-bold text-white">Neon Sci-Fi</div>
<div className="text-[10px] text-slate-400">Cyan & Purple Slate</div>
</button>
<button
type="button"
onClick={() => {
updateProfileField("accentColor", "#f97316");
updateProfileField("secondaryColor", "#f59e0b");
updateProfileField("backgroundColor", "#0a0a0a");
updateProfileField("cardBgColor", "#171717");
updateProfileField("fontFamily", "serif");
}}
className="p-3 bg-slate-950 border border-orange-500/40 hover:border-orange-400 rounded-xl text-left space-y-2 cursor-pointer transition-all"
>
<div className="flex gap-1.5">
<div className="w-4 h-4 rounded-full bg-[#f97316]" />
<div className="w-4 h-4 rounded-full bg-[#f59e0b]" />
<div className="w-4 h-4 rounded-full bg-[#0a0a0a] border border-slate-700" />
</div>
<div className="text-xs font-bold text-white">Samt & Peach</div>
<div className="text-[10px] text-slate-400">Orange & Amber Dark</div>
</button>
<button
type="button"
onClick={() => {
updateProfileField("accentColor", "#10b981");
updateProfileField("secondaryColor", "#84cc16");
updateProfileField("backgroundColor", "#064e3b");
updateProfileField("cardBgColor", "#022c22");
updateProfileField("fontFamily", "serif");
}}
className="p-3 bg-slate-950 border border-emerald-500/40 hover:border-emerald-400 rounded-xl text-left space-y-2 cursor-pointer transition-all"
>
<div className="flex gap-1.5">
<div className="w-4 h-4 rounded-full bg-[#10b981]" />
<div className="w-4 h-4 rounded-full bg-[#84cc16]" />
<div className="w-4 h-4 rounded-full bg-[#064e3b] border border-slate-700" />
</div>
<div className="text-xs font-bold text-white">Wald & Salbei</div>
<div className="text-[10px] text-slate-400">Emerald & Lime Forest</div>
</button>
<button
type="button"
onClick={() => {
updateProfileField("accentColor", "#ec4899");
updateProfileField("secondaryColor", "#8b5cf6");
updateProfileField("backgroundColor", "#09090b");
updateProfileField("cardBgColor", "#18181b");
updateProfileField("fontFamily", "sans");
}}
className="p-3 bg-slate-950 border border-pink-500/40 hover:border-pink-400 rounded-xl text-left space-y-2 cursor-pointer transition-all"
>
<div className="flex gap-1.5">
<div className="w-4 h-4 rounded-full bg-[#ec4899]" />
<div className="w-4 h-4 rounded-full bg-[#8b5cf6]" />
<div className="w-4 h-4 rounded-full bg-[#09090b] border border-slate-700" />
</div>
<div className="text-xs font-bold text-white">Obsidian Magenta</div>
<div className="text-[10px] text-slate-400">Pink & Violet Dark</div>
</button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 pt-2">
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Haupt-Akzentfarbe</label>
<div className="flex gap-2 items-center">
<input
type="color"
value={profile.accentColor || "#06b6d4"}
onChange={(e) => updateProfileField("accentColor", e.target.value)}
className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1"
/>
<input
type="text"
value={profile.accentColor || "#06b6d4"}
onChange={(e) => updateProfileField("accentColor", e.target.value)}
className="flex-1 px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Sekundär-Farbe</label>
<div className="flex gap-2 items-center">
<input
type="color"
value={profile.secondaryColor || "#a855f7"}
onChange={(e) => updateProfileField("secondaryColor", e.target.value)}
className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1"
/>
<input
type="text"
value={profile.secondaryColor || "#a855f7"}
onChange={(e) => updateProfileField("secondaryColor", e.target.value)}
className="flex-1 px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Hintergrundfarbe (Seiten-Canvas)</label>
<div className="flex gap-2 items-center">
<input
type="color"
value={profile.backgroundColor || "#020617"}
onChange={(e) => updateProfileField("backgroundColor", e.target.value)}
className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1"
/>
<input
type="text"
value={profile.backgroundColor || "#020617"}
onChange={(e) => updateProfileField("backgroundColor", e.target.value)}
className="flex-1 px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Karten-Hintergrundfarbe</label>
<div className="flex gap-2 items-center">
<input
type="color"
value={profile.cardBgColor || "#0f172a"}
onChange={(e) => updateProfileField("cardBgColor", e.target.value)}
className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1"
/>
<input
type="text"
value={profile.cardBgColor || "#0f172a"}
onChange={(e) => updateProfileField("cardBgColor", e.target.value)}
className="flex-1 px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Schriftart / Typografie</label>
<select
value={profile.fontFamily || "sans"}
onChange={(e) => updateProfileField("fontFamily", e.target.value as any)}
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"
>
<option value="sans">Moderne Sans-Serif (Standard)</option>
<option value="serif">Klassische Serif (Elegante Buchschrift)</option>
<option value="mono">Futuristische Monospace (Code/Tech)</option>
</select>
</div>
<div className="space-y-2 md:col-span-2 pt-2">
<label className="text-xs font-mono text-slate-400 uppercase font-bold text-white flex items-center gap-2">
<ImageIcon className="w-4 h-4 text-cyan-400" />
<span>Hero-Hintergrundbild (optional)</span>
</label>
<ImagePicker
value={profile.heroBannerUrl || ""}
onChange={(url) => updateProfileField("heroBannerUrl", url)}
label="Hero Banner-Bild"
/>
<p className="text-[10px] text-slate-500">Wenn angegeben, wird dieses Bild als stimmungsvoller Hintergrund im oberen Bereich der Webseite hinterlegt.</p>
</div>
</div>
</div>
)}
{/* TAB 2: Projects manager */}
{activeTab === "projects" && (
<div className="space-y-6">
{editingProjectId === null ? (
<div className="p-6 bg-slate-900 border border-slate-800 rounded-2xl space-y-4">
<div className="flex items-center justify-between border-b border-slate-800 pb-4">
<div>
<h2 className="text-lg font-bold text-white">Aktuelle Projekte</h2>
<p className="text-xs text-slate-400">Verwalten Sie Ihre laufenden Schreibarbeiten und zeigen Sie Ihren Fortschritt.</p>
</div>
<button
onClick={() => {
setEditingProjectId("new");
setProjectForm({ title: "", status: "Konzept", progress: 0, description: "", imageUrl: "", spotifyPlaylistId: "" });
}}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer"
>
<Plus className="w-4 h-4" />
<span>Neues Projekt anlegen</span>
</button>
</div>
<div className="space-y-4">
{profile.projects.length === 0 ? (
<p className="text-xs text-slate-500 text-center py-6 font-mono">Keine laufenden Projekte eingetragen.</p>
) : (
profile.projects.map((proj) => (
<div key={proj.id} className="p-4 bg-slate-950 border border-slate-800 rounded-xl flex items-center justify-between gap-4">
<div className="space-y-1 flex-1">
<div className="flex items-center gap-2">
<span className="text-[10px] font-mono text-indigo-400 bg-indigo-950 px-2 py-0.5 rounded uppercase border border-indigo-900/30">
{proj.status}
</span>
<h4 className="text-sm font-bold text-white">{proj.title}</h4>
</div>
<p className="text-xs text-slate-400 line-clamp-1">{proj.description}</p>
<div className="flex items-center gap-2 w-full max-w-[200px] pt-1">
<div className="h-1 bg-slate-800 rounded-full w-full overflow-hidden">
<div className="h-full bg-cyan-400 rounded-full" style={{ width: `${proj.progress}%` }} />
</div>
<span className="text-[10px] font-mono text-slate-550 text-slate-400">{proj.progress}%</span>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => {
setEditingProjectId(proj.id);
setProjectForm(proj);
}}
className="p-1.5 hover:bg-slate-800 rounded-lg text-slate-400 hover:text-white transition-all cursor-pointer"
title="Bearbeiten"
>
<Edit2 className="w-4 h-4" />
</button>
<button
onClick={() => handleDeleteProject(proj.id)}
className="p-1.5 hover:bg-rose-950 rounded-lg text-slate-400 hover:text-rose-450 text-rose-400 transition-all cursor-pointer"
title="Löschen"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
))
)}
</div>
</div>
) : (
/* Edit/Create Project Form */
<div className="p-6 bg-slate-900 border border-slate-800 rounded-2xl space-y-4">
<h3 className="text-base font-bold text-white">
{editingProjectId === "new" ? "Neues Schreibprojekt erschaffen" : "Schreibprojekt editieren"}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Projekttitel</label>
<input
type="text"
value={projectForm.title || ""}
onChange={(e) => setProjectForm({ ...projectForm, title: 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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Entwicklungsstatus</label>
<input
type="text"
value={projectForm.status || ""}
onChange={(e) => setProjectForm({ ...projectForm, status: e.target.value })}
placeholder="z.B. Schreibphase, Lektorat, Konzept"
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"
/>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-xs font-mono text-slate-200 uppercase flex justify-between">
<span>Schreibfortschritt</span>
<span className="text-indigo-400 font-bold">{projectForm.progress || 0}%</span>
</label>
<input
type="range"
min="0"
max="100"
value={projectForm.progress || 0}
onChange={(e) => setProjectForm({ ...projectForm, progress: parseInt(e.target.value) })}
className="w-full h-2 bg-slate-800 rounded-lg appearance-none cursor-pointer accent-cyan-400"
/>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-xs font-mono text-slate-400 uppercase">Kurzbeschreibung / Vorschau (Markdown & Links unterstützt)</label>
<textarea
rows={3}
value={projectForm.description || ""}
onChange={(e) => setProjectForm({ ...projectForm, description: 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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase font-bold text-white">Detailbild</label>
<ImagePicker
value={projectForm.imageUrl || ""}
onChange={(url) => setProjectForm({ ...projectForm, imageUrl: url })}
label="Projekt-Detailbild"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase flex items-center gap-1">
<Music className="w-3.5 h-3.5" />
<span>Spotify Playlist ID</span>
</label>
<input
type="text"
value={projectForm.spotifyPlaylistId || ""}
onChange={(e) => setProjectForm({ ...projectForm, spotifyPlaylistId: e.target.value })}
placeholder="z.B. 47R6ZAdk7Xf6M4i9mR0fP1"
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 font-mono"
/>
<span className="text-[9px] text-slate-500">Playlist-ID oder vollständige Spotify-Playlist-URL.</span>
</div>
</div>
<div className="flex items-center gap-3 pt-2">
<button
onClick={handleSaveProject}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg text-xs font-bold flex items-center gap-1.5 cursor-pointer"
>
<Check className="w-4 h-4" />
<span>Projekt sichern</span>
</button>
<button
onClick={() => setEditingProjectId(null)}
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg text-xs font-mono cursor-pointer"
>
Abbrechen
</button>
</div>
</div>
)}
</div>
)}
{/* TAB 3: Books shelf manager */}
{activeTab === "books" && (
<div className="space-y-6">
{editingBookId === null ? (
<div className="p-6 bg-slate-900 border border-slate-800 rounded-2xl space-y-4">
<div className="flex items-center justify-between border-b border-slate-800 pb-4">
<div>
<h2 className="text-lg font-bold text-white">Bücherregal verwalten</h2>
<p className="text-xs text-slate-400">Fügen Sie Bücher hinzu oder bearbeiten Sie Beschreibungen und Spotify-Playlists.</p>
</div>
<button
onClick={() => {
setEditingBookId("new");
setBookForm({
title: "", subtitle: "", coverUrl: "", description: "",
publishedYear: new Date().getFullYear(), buyLink: "",
spotifyPlaylistId: "", genres: [], isSpotlight: false, spotlightBadge: ""
});
setGenreInputRaw("");
// Preset Assistant blurb writer values to blank
setAiTitle("");
setAiIdeas("");
setAiResult("");
}}
className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer"
>
<Plus className="w-4 h-4" />
<span>Neues Buch hinzufügen</span>
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{profile.books.length === 0 ? (
<p className="text-xs text-slate-500 text-center py-6 font-mono col-span-3">Keine Bücher im virtuellen Regal.</p>
) : (
profile.books.map((b) => (
<div key={b.id} className="p-4 bg-slate-950 border border-slate-800 rounded-xl space-y-3 flex flex-col justify-between">
<div className="space-y-3">
<div className="w-24 aspect-[2/3] mx-auto rounded-lg overflow-hidden border border-slate-800 bg-slate-900 relative">
<img src={b.coverUrl || undefined} alt={b.title} className="w-full h-full object-cover" referrerPolicy="no-referrer" />
{b.isSpotlight && (
<span className="absolute top-1 right-1 bg-amber-500 text-slate-950 p-1 rounded-full shadow-md" title="Spotlight / Neuerscheinung">
<Sparkles className="w-3 h-3" />
</span>
)}
</div>
<div className="text-center space-y-0.5">
<h4 className="text-sm font-bold text-white line-clamp-1">{b.title}</h4>
<p className="text-[10px] text-indigo-400 font-mono italic uppercase">{b.subtitle || "Einzelband"}</p>
<span className="text-[10px] text-slate-500 font-mono">Datum/Jahr: {b.publishedYear}</span>
</div>
</div>
<div className="flex items-center justify-center gap-2 pt-2 border-t border-slate-900">
<button
onClick={() => {
setEditingBookId(b.id);
setBookForm(b);
setGenreInputRaw(b.genres ? b.genres.join(", ") : "");
setAiTitle(b.title || "");
setAiResult("");
}}
className="px-3 py-1.5 bg-slate-900 hover:bg-slate-800 border border-slate-800 hover:border-slate-700 rounded-lg text-xs font-mono text-slate-300 flex items-center gap-1 cursor-pointer"
>
<Edit2 className="w-3.5 h-3.5" />
<span>Editieren</span>
</button>
<button
onClick={() => handleDeleteBook(b.id)}
className="p-1.5 hover:bg-rose-950 text-rose-400 rounded-lg transition-all border border-transparent hover:border-rose-900 cursor-pointer"
title="Löschen"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
))
)}
</div>
</div>
) : (
/* Edit/Create Book Form with Integrated AI Synopsis generator */
<div className="space-y-6">
<div className="p-6 bg-slate-900 border border-slate-800 rounded-2xl space-y-4">
<div className="flex items-center justify-between border-b border-slate-800 pb-3">
<h3 className="text-base font-bold text-white">
{editingBookId === "new" ? "Neues Buch ins Regal stellen" : "Buchinformationen editieren"}
</h3>
<span className="text-[10px] text-slate-500 font-mono">ID: {editingBookId}</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Spotlight Section Toggle inside Book Form */}
<div className="md:col-span-2 p-4 bg-amber-950/30 border border-amber-500/40 rounded-xl space-y-3">
<div className="flex items-center gap-3">
<input
type="checkbox"
id="isSpotlightCheckbox"
checked={!!bookForm.isSpotlight}
onChange={(e) => setBookForm({ ...bookForm, isSpotlight: e.target.checked })}
className="w-4 h-4 rounded accent-amber-500 cursor-pointer"
/>
<label htmlFor="isSpotlightCheckbox" className="text-sm font-bold text-amber-300 cursor-pointer flex items-center gap-2">
<Sparkles className="w-4 h-4 text-amber-400" />
<span>Als Neuerscheinung / Buch-Spotlight auf der Homepage hervorheben</span>
</label>
</div>
<p className="text-xs text-slate-400 pl-7">
Wenn aktiviert, wird dieses Werk in einer prominenten Highlight-Kachel zwischen "Über mich" und "Aktuelle Projekte" platziert.
</p>
{bookForm.isSpotlight && (
<div className="pl-7 pt-1 space-y-1">
<label className="text-[10px] font-mono text-amber-400 uppercase font-semibold">Individueller Badge-Text (optional)</label>
<input
type="text"
value={bookForm.spotlightBadge || ""}
onChange={(e) => setBookForm({ ...bookForm, spotlightBadge: e.target.value })}
placeholder="z.B. Neuerscheinung, Vorbestellbar, Herbst-Highlight..."
className="w-full px-3 py-1.5 bg-slate-950 border border-slate-800 rounded-lg text-white outline-none focus:border-amber-500 text-xs"
/>
<p className="text-[10px] text-slate-500">
Wenn leer, wird automatisch "Erscheint am [Datum]" (bei Zukunftsdatum) oder "Neuerscheinung" verwendet.
</p>
</div>
)}
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Buchtitel</label>
<input
type="text"
value={bookForm.title || ""}
onChange={(e) => {
setBookForm({ ...bookForm, title: e.target.value });
setAiTitle(e.target.value);
}}
placeholder="z.B. Caelum: Ruf der Sterne"
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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Untertitel / Reihe</label>
<input
type="text"
value={bookForm.subtitle || ""}
onChange={(e) => setBookForm({ ...bookForm, subtitle: e.target.value })}
placeholder="z.B. Arche-Chroniken I"
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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase font-bold text-white">Cover-Bild</label>
<ImagePicker
value={bookForm.coverUrl || ""}
onChange={(url) => setBookForm({ ...bookForm, coverUrl: url })}
label="Buch-Cover"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Veröffentlichungsdatum / -jahr</label>
<input
type="text"
value={bookForm.publishedYear || ""}
onChange={(e) => setBookForm({ ...bookForm, publishedYear: e.target.value })}
placeholder="z.B. 15.10.2026 oder 2025"
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"
/>
<p className="text-[10px] text-slate-500">Unterstützt Jahreszahlen (2026) und konkrete Daten (15.10.2026).</p>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase flex items-center gap-1">
<Eye className="w-3.5 h-3.5" />
<span>Buy-Link / Shop-Link (z.B. Amazon, Thalia)</span>
</label>
<input
type="text"
value={bookForm.buyLink || ""}
onChange={(e) => setBookForm({ ...bookForm, buyLink: e.target.value })}
placeholder="https://..."
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"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase flex items-center gap-1">
<Music className="w-3.5 h-3.5" />
<span>Spotify Playlist ID (Webplayer Embed)</span>
</label>
<input
type="text"
value={bookForm.spotifyPlaylistId || ""}
onChange={(e) => setBookForm({ ...bookForm, spotifyPlaylistId: e.target.value })}
placeholder="z.B. 47R6ZAdk7Xf6M4i9mR0fP1"
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 font-mono"
/>
<span className="text-[9px] text-slate-500">Nur den ID-String eintragen, nicht die komplette URL.</span>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-xs font-mono text-slate-400 uppercase">Genres (Kommagetrennt)</label>
<input
type="text"
value={genreInputRaw}
onChange={(e) => setGenreInputRaw(e.target.value)}
placeholder="z.B. Science-Fiction, Cyberpunk, Drama"
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"
/>
<p className="text-[10px] text-slate-500">Trennen Sie mehrere Genres einfach durch Kommas.</p>
</div>
{/* Buch-Klappentext Synopsis / Description */}
<div className="space-y-1 md:col-span-2">
<div className="flex items-center justify-between">
<label className="text-xs font-mono text-slate-400 uppercase">Buch-Klappentext (Synopsis)</label>
<span className="text-[10px] text-purple-400 font-mono">Markdown & Absätze erlaubt</span>
</div>
<textarea
rows={6}
value={bookForm.description || ""}
onChange={(e) => setBookForm({ ...bookForm, description: 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 font-sans"
/>
</div>
</div>
<div className="flex flex-wrap items-center gap-3 pt-4 border-t border-slate-800">
<button
onClick={handleSaveBook}
className="px-4 py-2.5 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg text-xs font-bold flex items-center gap-1.5 cursor-pointer"
>
<Check className="w-4 h-4" />
<span>Sichern & ins Regal stellen</span>
</button>
<button
onClick={() => setEditingBookId(null)}
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg text-xs font-mono cursor-pointer"
>
Abbrechen
</button>
</div>
</div>
{/* 21st Century KI Integration - Gemini 3.5 Blurb generator tool panel */}
<div className="p-6 bg-gradient-to-r from-slate-900 to-indigo-950 border border-indigo-900/50 rounded-2xl space-y-4">
<div className="flex items-center justify-between border-b border-indigo-900/30 pb-3">
<h4 className="text-sm font-bold text-white flex items-center gap-2">
<Sparkles className="w-4 h-4 text-cyan-400" />
<span>KI-Schreibwerkstatt: Klappentext-Generator</span>
</h4>
<span className="text-[10px] text-cyan-400 font-mono tracking-wide">Powered by Gemini 3.5</span>
</div>
<div className="space-y-3 text-xs leading-relaxed text-slate-300">
<p>
Haben Sie eine Schreibblockade? Geben Sie ein paar Stichworte Ihrer Ideen ein. Unser KI-Assistent formuliert für Sie einen verkaufsfördernden und atmosphärischen Klappentext passend zur Identität!
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-1">
<label className="text-[10px] font-mono text-slate-400 uppercase">Assistenten-Buchtitel</label>
<input
type="text"
value={aiTitle}
onChange={(e) => setAiTitle(e.target.value)}
className="w-full px-3 py-1.5 bg-slate-950 border border-slate-800 rounded-lg text-white text-xs outline-none focus:border-indigo-500"
placeholder="Aktueller Titel"
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-mono text-slate-400 uppercase">Genre / Stilvorgabe</label>
<input
type="text"
value={aiGenre}
onChange={(e) => setAiGenre(e.target.value)}
className="w-full px-3 py-1.5 bg-slate-950 border border-slate-800 rounded-lg text-white text-xs outline-none focus:border-indigo-500"
placeholder={activeProfile === "scifi" ? "z.B. Space-Opera, Runenmagie" : "z.B. Elegante Romanze, Verbotenes Begehren"}
/>
</div>
<div className="space-y-1">
<label className="text-[10px] font-mono text-slate-400 uppercase">Tonalität / Stimmung</label>
<select
value={aiTone}
onChange={(e) => setAiTone(e.target.value)}
className="w-full px-3 py-1.5 bg-slate-950 border border-slate-800 rounded-lg text-white text-xs outline-none focus:border-indigo-500"
>
<option value="Geheimnisvoll & Episch">Geheimnisvoll & Episch</option>
<option value="Dystopisch & Düster">Dystopisch & Düster</option>
<option value="Sinnlich & Knisternd">Sinnlich & Knisternd (Erotisch)</option>
<option value="Emotional & Melancholisch">Emotional & Melancholisch</option>
<option value="Actionreich & Fesselnd">Actionreich & Fesselnd</option>
</select>
</div>
<div className="space-y-1 md:col-span-3">
<label className="text-[10px] font-mono text-slate-400 uppercase">Stichpunkte, Motive, Kontexte (Ideen)</label>
<textarea
rows={3}
value={aiIdeas}
onChange={(e) => setAiIdeas(e.target.value)}
placeholder="z.B. Raumschiffabsturz, uralter Fluch auf einer Wüstenwelt, Geheimidentität und verbotene Leidenschaft..."
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-xs"
/>
</div>
</div>
<div className="flex justify-between items-center pt-2">
<button
onClick={handleGenerateAISynopsis}
disabled={aiLoading}
className="px-4 py-2 bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-500 hover:to-indigo-500 text-white rounded-lg text-xs font-bold flex items-center gap-1.5 cursor-pointer disabled:opacity-50"
>
{aiLoading ? <RefreshCw className="w-3.5 h-3.5 animate-spin" /> : <Sparkles className="w-3.5 h-3.5 text-cyan-300" />}
<span>Synopsis generieren und ins Formular eintragen</span>
</button>
</div>
{aiResult && (
<div className="p-4 bg-slate-950/80 border border-indigo-950 rounded-xl space-y-2 text-xs font-sans">
<div className="text-[10px] font-mono text-cyan-400 uppercase">Generierter Entwurf:</div>
<p className="text-slate-350 leading-relaxed italic whitespace-pre-wrap">{aiResult}</p>
</div>
)}
</div>
</div>
)}
</div>
)}
{/* TAB: LEGAL DOCUMENTS (IMPRESSUM & DATENSCHUTZ) */}
{activeTab === "legal" && (
<div className="p-6 md:p-8 space-y-8">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 border-b border-slate-800 pb-6">
<div>
<h3 className="text-xl font-bold text-white flex items-center gap-2">
<Scale className="w-5 h-5 text-indigo-400" />
<span>Rechtliche Pflichtangaben & Dokumente</span>
</h3>
<p className="text-xs text-slate-400 mt-1">
Erstellen und verwalten Sie Impressum, Datenschutzerklärungen und individuelle Rechtsbelehrungen für Ihre Autorenprofile.
</p>
</div>
{!editingLegalId && (
<button
onClick={() => {
setEditingLegalId("new");
setLegalForm({
title: "Impressum",
type: "impressum",
updatedAt: new Date().toISOString().split("T")[0],
assignedProfiles: ["scifi", "erotica", "clara", "renee"],
content: ""
});
setLegalPreviewMode("edit");
}}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer shadow-lg shadow-indigo-950/50"
>
<Plus className="w-4 h-4" />
<span>Neues Dokument anlegen</span>
</button>
)}
</div>
{/* Document List when not editing */}
{!editingLegalId ? (
<div className="space-y-6">
{(!authorData.legalDocuments || authorData.legalDocuments.length === 0) ? (
<div className="p-12 text-center bg-slate-900/40 border border-slate-800 border-dashed rounded-2xl space-y-4">
<Scale className="w-10 h-10 text-slate-600 mx-auto" />
<div>
<h4 className="text-sm font-bold text-white">Noch keine rechtlichen Dokumente angelegt</h4>
<p className="text-xs text-slate-400 max-w-md mx-auto mt-1">
Legen Sie ein Impressum oder eine Datenschutzerklärung an, um rechtliche Transparenz auf Ihren Portfolioseiten zu gewährleisten.
</p>
</div>
<button
onClick={() => {
setEditingLegalId("new");
setLegalForm({
title: "Impressum",
type: "impressum",
updatedAt: new Date().toISOString().split("T")[0],
assignedProfiles: ["scifi", "erotica", "clara", "renee"],
content: ""
});
}}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-xs font-bold transition-all inline-flex items-center gap-2 cursor-pointer"
>
<Plus className="w-4 h-4" />
<span>Erstes Dokument anlegen</span>
</button>
</div>
) : (
<div className="grid grid-cols-1 gap-4">
{authorData.legalDocuments.map((doc) => {
const profileNamesMap = {
scifi: authorData.scifi.name,
erotica: authorData.erotica.name,
clara: authorData.clara.name,
renee: authorData.renee.name
};
return (
<div
key={doc.id}
className="p-5 bg-slate-900 border border-slate-800 hover:border-slate-700 rounded-2xl transition-all space-y-4 shadow-sm"
>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div className="flex items-center gap-3">
<div className="p-2.5 bg-slate-950 border border-slate-800 rounded-xl text-indigo-400">
{doc.type === "impressum" ? (
<Scale className="w-5 h-5" />
) : doc.type === "privacy" ? (
<ShieldCheck className="w-5 h-5 text-emerald-400" />
) : (
<FileText className="w-5 h-5 text-cyan-400" />
)}
</div>
<div>
<div className="flex items-center gap-2">
<h4 className="text-base font-bold text-white">{doc.title}</h4>
<span className="px-2 py-0.5 bg-slate-800 border border-slate-700 text-slate-300 text-[10px] font-mono rounded-md">
{doc.type === "impressum" ? "Impressum (§ 5 DDG)" : doc.type === "privacy" ? "Datenschutz (DSGVO)" : "Individuell"}
</span>
</div>
<span className="text-[11px] text-slate-500 font-mono">
Stand: {doc.updatedAt || "Aktuell"}
</span>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => handleDuplicateLegalDocument(doc)}
title="Dokument duplizieren"
className="p-2 bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white rounded-lg text-xs font-semibold transition-colors cursor-pointer flex items-center gap-1.5"
>
<Copy className="w-3.5 h-3.5" />
<span className="hidden sm:inline">Kopieren</span>
</button>
<button
onClick={() => {
setEditingLegalId(doc.id);
setLegalForm({ ...doc });
setLegalPreviewMode("edit");
}}
className="px-3 py-2 bg-indigo-600/20 hover:bg-indigo-600/30 text-indigo-300 hover:text-indigo-200 border border-indigo-500/30 rounded-lg text-xs font-semibold transition-colors cursor-pointer flex items-center gap-1.5"
>
<Edit2 className="w-3.5 h-3.5" />
<span>Bearbeiten</span>
</button>
<button
onClick={() => handleDeleteLegalDocument(doc.id)}
title="Dokument löschen"
className="p-2 bg-rose-950/40 hover:bg-rose-900/60 text-rose-400 hover:text-rose-200 border border-rose-900/50 rounded-lg transition-colors cursor-pointer"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
</div>
{/* Assigned Profiles Badges */}
<div className="pt-2 border-t border-slate-800/60 flex flex-wrap items-center gap-2 text-xs">
<span className="text-[11px] font-mono text-slate-500 mr-1">Freigegeben für:</span>
{(["scifi", "erotica", "clara", "renee"] as const).map((pKey) => {
const isAssigned = doc.assignedProfiles?.includes(pKey);
return (
<span
key={pKey}
className={`px-2.5 py-0.5 rounded-full text-[11px] font-medium border ${
isAssigned
? "bg-indigo-950/60 border-indigo-700/60 text-indigo-300"
: "bg-slate-950/40 border-slate-800/60 text-slate-600 line-through"
}`}
>
{profileNamesMap[pKey]}
</span>
);
})}
</div>
{/* Short preview of content */}
<div className="p-3 bg-slate-950/60 rounded-xl border border-slate-800/40 text-xs text-slate-400 line-clamp-2 font-mono">
{doc.content ? doc.content.slice(0, 200) + (doc.content.length > 200 ? "..." : "") : "Kein Textinhalt hinterlegt."}
</div>
</div>
);
})}
</div>
)}
</div>
) : (
/* Document Editor Form */
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-6 md:p-8 space-y-6">
<div className="flex items-center justify-between border-b border-slate-800 pb-4">
<div className="flex items-center gap-2">
<Scale className="w-5 h-5 text-indigo-400" />
<h4 className="text-lg font-bold text-white">
{editingLegalId === "new" ? "Neues Rechtsdokument anlegen" : `Dokument bearbeiten: ${legalForm.title || ""}`}
</h4>
</div>
<div className="flex items-center gap-2 bg-slate-950 p-1 rounded-xl border border-slate-800">
<button
type="button"
onClick={() => setLegalPreviewMode("edit")}
className={`px-3 py-1 rounded-lg text-xs font-semibold transition-all cursor-pointer flex items-center gap-1.5 ${
legalPreviewMode === "edit"
? "bg-indigo-600 text-white shadow-sm"
: "text-slate-400 hover:text-white"
}`}
>
<Edit2 className="w-3.5 h-3.5" />
<span>Bearbeiten</span>
</button>
<button
type="button"
onClick={() => setLegalPreviewMode("preview")}
className={`px-3 py-1 rounded-lg text-xs font-semibold transition-all cursor-pointer flex items-center gap-1.5 ${
legalPreviewMode === "preview"
? "bg-indigo-600 text-white shadow-sm"
: "text-slate-400 hover:text-white"
}`}
>
<Eye className="w-3.5 h-3.5" />
<span>Live-Vorschau</span>
</button>
</div>
</div>
{/* Form fields */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div className="space-y-1 sm:col-span-1">
<label className="text-xs font-mono text-slate-400 uppercase">Dokument-Titel</label>
<input
type="text"
value={legalForm.title || ""}
onChange={(e) => setLegalForm({ ...legalForm, title: e.target.value })}
placeholder="z.B. Impressum"
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"
/>
</div>
<div className="space-y-1 sm:col-span-1">
<label className="text-xs font-mono text-slate-400 uppercase">Dokumenttyp</label>
<select
value={legalForm.type || "impressum"}
onChange={(e) => setLegalForm({ ...legalForm, type: e.target.value as any })}
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"
>
<option value="impressum">Impressum (§ 5 DDG)</option>
<option value="privacy">Datenschutzerklärung (DSGVO)</option>
<option value="custom">Individuelles Rechtsdokument</option>
</select>
</div>
<div className="space-y-1 sm:col-span-1">
<label className="text-xs font-mono text-slate-400 uppercase">Stand / Aktualisierungsdatum</label>
<input
type="text"
value={legalForm.updatedAt || ""}
onChange={(e) => setLegalForm({ ...legalForm, updatedAt: e.target.value })}
placeholder="z.B. 2026-08-14"
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 font-mono"
/>
</div>
</div>
{/* Profile Assignment Selection */}
<div className="p-4 bg-slate-950/70 border border-slate-800 rounded-xl space-y-3">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<div>
<h5 className="text-xs font-mono text-indigo-400 uppercase tracking-wider">
Freigabe für Autorenprofile & Seiten
</h5>
<p className="text-[11px] text-slate-400">
Wählen Sie aus, auf welchen Portfolioseiten dieses Dokument im Footer als Link eingeblendet wird.
</p>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => handleAssignAllProfiles(true)}
className="px-2.5 py-1 bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white rounded-lg text-xs font-mono transition-colors cursor-pointer"
>
Alle auswählen
</button>
<button
type="button"
onClick={() => handleAssignAllProfiles(false)}
className="px-2.5 py-1 bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white rounded-lg text-xs font-mono transition-colors cursor-pointer"
>
Keine
</button>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3 pt-2">
{[
{ key: "scifi" as const, name: authorData.scifi.name, badge: "Sci-Fi" },
{ key: "erotica" as const, name: authorData.erotica.name, badge: "Erotik" },
{ key: "clara" as const, name: authorData.clara.name, badge: "Naturlyrik" },
{ key: "renee" as const, name: authorData.renee.name, badge: "Romantik" }
].map((item) => {
const checked = legalForm.assignedProfiles?.includes(item.key);
return (
<div
key={item.key}
onClick={() => toggleProfileAssignment(item.key)}
className={`p-3 rounded-xl border transition-all cursor-pointer flex items-center justify-between gap-2 select-none ${
checked
? "bg-indigo-950/40 border-indigo-500/60 text-white"
: "bg-slate-900/60 border-slate-800 text-slate-400 hover:border-slate-700"
}`}
>
<div className="truncate">
<span className="text-[10px] font-mono text-indigo-400 block">{item.badge}</span>
<span className="text-xs font-bold truncate block">{item.name}</span>
</div>
{checked ? (
<CheckSquare className="w-4 h-4 text-indigo-400 shrink-0" />
) : (
<Square className="w-4 h-4 text-slate-600 shrink-0" />
)}
</div>
);
})}
</div>
</div>
{/* Text Editor or Preview */}
{legalPreviewMode === "edit" ? (
<div className="space-y-2">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<label className="text-xs font-mono text-slate-400 uppercase flex items-center gap-1.5">
<span>Dokument-Inhalt (Markdown unterstützt)</span>
</label>
<div className="flex flex-wrap items-center gap-1.5 text-[11px]">
<span className="text-slate-500">Mustertexte einfügen:</span>
<button
type="button"
onClick={() => {
if (legalForm.content && !confirm("Aktuellen Inhalt durch das DDG-Impressum-Muster ersetzen?")) return;
setLegalForm({
...legalForm,
title: "Impressum",
type: "impressum",
content: `### Angaben gemäß § 5 Digitale-Dienste-Gesetz (DDG)
**Vor- und Nachname**
Straße und Hausnummer
PLZ Ort
Deutschland
### Kontakt
**E-Mail:** kontakt@example.org
**Website:** https://example.org
### Redaktionell verantwortlich
Vor- und Nachname
Straße und Hausnummer
PLZ Ort
### Urheberrecht
Die auf dieser Internetpräsenz präsentierten literarischen Werke und Inhalte sind urheberrechtlich geschützt.
### EU-Streitschlichtung
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: https://ec.europa.eu/consumers/odr/
Unsere E-Mail-Adresse finden Sie oben im Impressum.
### Verbraucherstreitbeilegung / Universalschlichtungsstelle
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.`
});
}}
className="px-2 py-0.5 bg-slate-800 hover:bg-slate-700 text-indigo-300 rounded font-mono cursor-pointer"
>
+ Impressum-Muster
</button>
<button
type="button"
onClick={() => {
if (legalForm.content && !confirm("Aktuellen Inhalt durch das DSGVO-Datenschutz-Muster ersetzen?")) return;
setLegalForm({
...legalForm,
title: "Datenschutzerklärung",
type: "privacy",
content: `### 1. Datenschutz auf einen Blick
**Allgemeine Hinweise**
Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können.
### 2. Verantwortliche Stelle
Verantwortlich für die Datenverarbeitung auf dieser Website ist:
**Vor- und Nachname**
Straße und Hausnummer
PLZ Ort
E-Mail: kontakt@example.org
### 3. Datenerfassung auf dieser Website
**Bereitstellung der Website und Erstellung der Logfiles**
Beim Aufrufen unserer Website werden durch den auf Ihrem Endgerät zum Einsatz kommenden Browser automatisch Informationen an den Server unserer Website gesendet. Diese Informationen werden temporär in einem sogenannten Logfile gespeichert:
- Browsertyp und Browserversion
- Verwendetes Betriebssystem
- Referrer URL (die zuvor besuchte Seite)
- Hostname des zugreifenden Rechners
- Uhrzeit der Serveranfrage
- IP-Adresse
Die Rechtsgrundlage für die Datenverarbeitung ist Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse zur Gewährleistung eines reibungslosen Verbindungsaufbaus und der Systemsicherheit).
**Kontaktaufnahme per E-Mail**
Wenn Sie uns per E-Mail Anfragen zukommen lassen, werden Ihre Angaben inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter (Art. 6 Abs. 1 lit. b bzw. lit. f DSGVO).
### 4. Eingebundene Dienste Dritter
**Spotify Player & Externe Buch-Links**
Auf einigen Seiten sind Hörbeispiele bzw. Playlists des Dienstes Spotify eingebunden oder Links zu Buchhändlern (z. B. Amazon, Thalia) gesetzt. Wenn Sie diese externen Links anklicken oder mit externen Playern interagieren, werden Daten an die jeweiligen Anbieter übertragen.
### 5. Ihre Rechte als betroffene Person
Sie haben im Rahmen der geltenden gesetzlichen Bestimmungen jederzeit das Recht auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten, deren Herkunft und Empfänger und den Zweck der Datenverarbeitung und ggf. ein Recht auf Berichtigung, Sperrung oder Löschung dieser Daten.`
});
}}
className="px-2 py-0.5 bg-slate-800 hover:bg-slate-700 text-emerald-300 rounded font-mono cursor-pointer"
>
+ Datenschutz-Muster
</button>
</div>
</div>
<textarea
rows={14}
value={legalForm.content || ""}
onChange={(e) => setLegalForm({ ...legalForm, content: e.target.value })}
placeholder="Tragen Sie hier Ihren Gesetzestext oder Ihre Datenschutzerklärung ein..."
className="w-full px-4 py-3 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-xs font-mono leading-relaxed"
/>
<p className="text-[11px] text-slate-500">
Formatierungstipp: Überschriften mit <code className="text-slate-300">### Titel</code>, Fettgedruckt mit <code className="text-slate-300">**Text**</code>, Listen mit <code className="text-slate-300">- Punkt</code>, Links mit <code className="text-slate-300">[Titel](URL)</code>.
</p>
</div>
) : (
/* Live Preview Box */
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Live-Vorschau der Darstellung:</label>
<div className="p-6 bg-slate-950 border border-slate-800 rounded-xl max-h-96 overflow-y-auto markdown-body text-slate-300 text-xs leading-relaxed space-y-3">
{legalForm.content ? (
<Markdown>{legalForm.content}</Markdown>
) : (
<p className="text-slate-500 italic">Noch kein Text zum Anzeigen vorhanden.</p>
)}
</div>
</div>
)}
{/* Actions */}
<div className="flex items-center justify-end gap-3 pt-4 border-t border-slate-800">
<button
type="button"
onClick={() => {
setEditingLegalId(null);
setLegalForm({});
}}
className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white rounded-xl text-xs font-semibold transition-colors cursor-pointer"
>
Abbrechen
</button>
<button
type="button"
onClick={handleSaveLegalDocument}
disabled={loading || !legalForm.title?.trim()}
className="px-5 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-xl text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer disabled:opacity-50 shadow-lg shadow-indigo-950/50"
>
<Save className="w-4 h-4" />
<span>{loading ? "Speichern..." : "Dokument speichern"}</span>
</button>
</div>
</div>
)}
</div>
)}
</div>
</div>
</div>
);
}