diff --git a/server.ts b/server.ts index 06655b8..8605944 100644 --- a/server.ts +++ b/server.ts @@ -30,7 +30,27 @@ async function initDatabase(): Promise { await fs.mkdir(DATA_DIR, { recursive: true }); try { const existingData = await fs.readFile(DATA_FILE, "utf-8"); - return JSON.parse(existingData) as AuthorData; + const parsed = JSON.parse(existingData) as AuthorData; + + // Auto-heal database: make sure all profiles from defaultAuthorData exist + let modified = false; + if (!parsed.scifi) { + parsed.scifi = defaultAuthorData.scifi; + modified = true; + } + if (!parsed.erotica) { + parsed.erotica = defaultAuthorData.erotica; + modified = true; + } + if (!parsed.clara) { + parsed.clara = defaultAuthorData.clara; + modified = true; + } + + if (modified) { + await fs.writeFile(DATA_FILE, JSON.stringify(parsed, null, 2), "utf-8"); + } + return parsed; } catch { // If file doesn't exist, seed it with default data await fs.writeFile(DATA_FILE, JSON.stringify(defaultAuthorData, null, 2), "utf-8"); @@ -116,7 +136,7 @@ app.post("/api/admin/login", (req, res) => { // 3. Save modified profile configurations (About, Projects, Books) app.post("/api/admin/save-profile", verifyToken, async (req, res) => { const { profileKey, profileData } = req.body; - if (profileKey !== "scifi" && profileKey !== "erotica") { + if (profileKey !== "scifi" && profileKey !== "erotica" && profileKey !== "clara") { res.status(400).json({ error: "Ungültiger Profilschlüssel." }); return; } diff --git a/src/App.tsx b/src/App.tsx index 10fd809..4ce31e7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,8 +2,9 @@ import { useState, useEffect } from "react"; import { AuthorData } from "./types"; import SciFiPortfolio from "./components/SciFiPortfolio"; import EroticaPortfolio from "./components/EroticaPortfolio"; +import ClaraPortfolio from "./components/ClaraPortfolio"; import AdminPanel from "./components/AdminPanel"; -import { Lock, Eye, Sparkles, RefreshCw } from "lucide-react"; +import { Lock, Eye, Sparkles, RefreshCw, Leaf } from "lucide-react"; export default function App() { const [authorData, setAuthorData] = useState(null); @@ -71,18 +72,26 @@ export default function App() { return ; } - // 2. Sensual/Erotica Portfolio pseudonym - accessed ONLY via /sensual-moments (Strict content separation) - if (currentPath === "/sensual-moments" || currentPath.startsWith("/sensual-moments/")) { - return ; - } + // 2. Sensual/Erotica Portfolio pseudonym + // 3. Poetic Clara Finch Portfolio pseudonym + // 4. SciFi Daniel Hesse Portfolio + + const renderActivePortfolio = () => { + if (currentPath === "/sensual-moments" || currentPath.startsWith("/sensual-moments/")) { + return ; + } + if (currentPath === "/clara-finch" || currentPath.startsWith("/clara-finch/")) { + return ; + } + return ; + }; - // 3. Fallback: SciFi & Fantasy portfolio - accessed via `/` (Daniel Hesse identity) return ( <> - + {renderActivePortfolio()} - {/* Absolute floating administration bypass link hidden on the bottom right for convenience of testing/admin accessing */} -
+ {/* Absolute floating administration and pseudonym portal switcher on the bottom left (hover to show) */} +
{ @@ -90,12 +99,25 @@ export default function App() { window.history.pushState({}, "", "/admin"); setCurrentPath("/admin"); }} - className="text-slate-500 hover:text-cyan-400 font-bold flex items-center gap-1" + className="text-stone-500 hover:text-cyan-400 font-bold flex items-center gap-1 transition-colors" > Admin-Zentrale -
+
+ { + e.preventDefault(); + window.history.pushState({}, "", "/"); + setCurrentPath("/"); + }} + className={`font-bold flex items-center gap-1 transition-colors ${currentPath === "/" ? "text-cyan-400" : "text-stone-500 hover:text-cyan-400"}`} + > + + Sci-Fi & Fantasy + +
{ @@ -103,10 +125,23 @@ export default function App() { window.history.pushState({}, "", "/sensual-moments"); setCurrentPath("/sensual-moments"); }} - className="text-slate-500 hover:text-rose-400 font-bold flex items-center gap-1" + className={`font-bold flex items-center gap-1 transition-colors ${currentPath === "/sensual-moments" ? "text-orange-400" : "text-stone-500 hover:text-orange-400"}`} > - Sinnlich + Sinnlich (M.S. Velvet) + + diff --git a/src/components/AdminPanel.tsx b/src/components/AdminPanel.tsx index 1707afa..b38152d 100644 --- a/src/components/AdminPanel.tsx +++ b/src/components/AdminPanel.tsx @@ -19,8 +19,8 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) { // Author Data State const [authorData, setAuthorData] = useState(null); - // Selected Profile state ("scifi" vs "erotica") - const [activeProfile, setActiveProfile] = useState<"scifi" | "erotica">("scifi"); + // Selected Profile state ("scifi" vs "erotica" vs "clara") + const [activeProfile, setActiveProfile] = useState<"scifi" | "erotica" | "clara">("scifi"); // Sub-tab selection state ("profile" | "books" | "projects") const [activeTab, setActiveTab] = useState<"profile" | "books" | "projects">("profile"); @@ -97,7 +97,7 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) { }; // Profile save handler - const handleSaveProfileInfo = async (profileKey: "scifi" | "erotica") => { + const handleSaveProfileInfo = async (profileKey: "scifi" | "erotica" | "clara") => { if (!authorData) return; setError(""); setLoading(true); @@ -419,10 +419,16 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) { +
+ ))} +
+ + + {/* Bookshelf display */} +
+
+

+ + Gedruckte Werke +

+

Erschlossene Wortlandschaften. Wählen Sie einen Band, um tiefer hineinzuhören.

+
+ +
+ {data.books.map((book) => ( + + ))} +
+
+ + + + {/* Book details modular modal */} + {selectedBook && ( +
+
setSelectedBook(null)} + /> + +
+
+ + + +
+ + {/* Book Cover */} +
+
+ {selectedBook.title} +
+ {selectedBook.buyLink && ( + + Werk erwerben + + + )} +
+ + {/* Details text area */} +
+
+
+ + Erschienen: {selectedBook.publishedYear} + + {selectedBook.genres?.map((genre) => ( + + {genre} + + ))} +
+ +

+ {selectedBook.title} +

+ + {selectedBook.subtitle && ( +

+ {selectedBook.subtitle} +

+ )} + +
+ +

+ {selectedBook.description} +

+
+ + {/* Embedded Spotify player (Only if playlist is non-empty) */} + {selectedBook.spotifyPlaylistId && selectedBook.spotifyPlaylistId.trim() !== "" && ( +
+

+ + Klanglandschaft (Begleitende Playlist) +

+
+