webstack-author/src/App.tsx
2026-08-15 08:45:07 +02:00

195 lines
7.6 KiB
TypeScript

import { lazy, Suspense, useState, useEffect } from "react";
import { AuthorData } from "./types";
import { Lock, RefreshCw } from "lucide-react";
const SciFiPortfolio = lazy(() => import("./components/SciFiPortfolio"));
const EroticaPortfolio = lazy(() => import("./components/EroticaPortfolio"));
const ClaraPortfolio = lazy(() => import("./components/ClaraPortfolio"));
const ReneePortfolio = lazy(() => import("./components/ReneePortfolio"));
const AdminPanel = lazy(() => import("./components/AdminPanel"));
function LoadingScreen() {
return (
<div className="min-h-screen bg-slate-950 text-slate-100 flex flex-col items-center justify-center space-y-4">
<RefreshCw className="w-8 h-8 animate-spin text-cyan-400" />
<p className="font-mono text-xs text-slate-500 uppercase tracking-widest">Autoren-Portfolio wird geladen...</p>
</div>
);
}
export default function App() {
const [authorData, setAuthorData] = useState<AuthorData | null>(null);
const [loading, setLoading] = useState(true);
const [currentPath, setCurrentPath] = useState(window.location.pathname);
// Monitor location changes for custom routing
useEffect(() => {
const handleLocationChange = () => {
setCurrentPath(window.location.pathname);
};
// Listen to popstate for navigation back/forward support
window.addEventListener("popstate", handleLocationChange);
return () => {
window.removeEventListener("popstate", handleLocationChange);
};
}, []);
// Fetch profiles configuration from server
useEffect(() => {
fetch("/api/author-data")
.then((res) => res.json())
.then((data) => {
setAuthorData(data);
setLoading(false);
})
.catch((err) => {
console.error("Fehler beim Abrufen der Autoren-Informationen:", err);
setLoading(false);
});
}, []);
// Ermittelt die aktive Suite basierend auf Pfad-Überschreibungen oder dem Domainnamen (Hostname)
const getActiveSuite = (): "erotica" | "clara" | "scifi" | "renee" => {
const hostname = window.location.hostname.toLowerCase();
const profileKeys: ("erotica" | "clara" | "renee" | "scifi")[] = ["erotica", "clara", "renee", "scifi"];
// 1. Pfad-basierte Routen (haben Vorrang, z.B. für Vorschau & Direktlinks)
for (const key of profileKeys) {
const p = authorData?.[key];
const customPaths: string[] = p?.customPath
? p.customPath.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
: [];
// Statische Standard-Fallbacks
if (key === "erotica") customPaths.push("/sensual-moments", "/annie-slone", "/marc-velvet");
if (key === "clara") customPaths.push("/clara-finch", "/clara");
if (key === "renee") customPaths.push("/renee-heart", "/renee");
if (key === "scifi") customPaths.push("/sci-fi", "/daniel-hesse");
for (const prefix of customPaths) {
if (prefix) {
const formattedPrefix = prefix.startsWith("/") ? prefix : "/" + prefix;
if (currentPath === formattedPrefix || currentPath.startsWith(formattedPrefix + "/")) {
return key;
}
}
}
}
// 2. Dynamische Domain-basierte Routen (für Live-Domains)
for (const key of profileKeys) {
const p = authorData?.[key];
const customDomains: string[] = p?.customDomain
? p.customDomain.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
: [];
// Statische Standard-Fallbacks
if (key === "erotica") customDomains.push("annieslone.de", "marcvelvet.de");
if (key === "clara") customDomains.push("clarafinch.de", "clara-finch.de");
if (key === "renee") customDomains.push("reneeheart.de", "renee-heart.de");
if (key === "scifi") customDomains.push("hesse-sf.de", "danielhesse.de");
for (const domain of customDomains) {
// Entferne evtl. eingegebene Protokolle (http://, https://) oder führendes "www."
const cleanDomain = domain.replace(/^https?:\/\//, "").replace(/^www\./, "").trim();
if (cleanDomain && (hostname === cleanDomain || hostname === `www.${cleanDomain}`)) {
return key;
}
}
}
// Standard-Fallback (Daniel Hesse SciFi)
return "scifi";
};
// Dynamisch den Dokumententitel (Browsertab-Titel) basierend auf Suite und Autorendaten anpassen
useEffect(() => {
if (!authorData) {
document.title = "Lade Autoren-Portfolio...";
return;
}
if (currentPath === "/admin" || currentPath.startsWith("/admin/")) {
document.title = "Admin-Zentrale | Autor-Suiten";
return;
}
const activeSuite = getActiveSuite();
if (activeSuite === "erotica") {
document.title = authorData.erotica.name || "Annie Slone";
} else if (activeSuite === "clara") {
document.title = authorData.clara.name || "Clara Finch";
} else if (activeSuite === "renee") {
document.title = authorData.renee.name || "Renee Heart";
} else {
document.title = authorData.scifi.name || "Daniel Hesse";
}
}, [currentPath, authorData]);
const handleAdminLogout = () => {
// Relocate to landing site after administration work
window.history.pushState({}, "", "/");
setCurrentPath("/");
};
if (loading) {
return <LoadingScreen />;
}
if (!authorData) {
return (
<div className="min-h-screen bg-slate-950 text-slate-100 flex flex-col items-center justify-center p-4">
<div className="max-w-md p-6 bg-slate-900 border border-slate-800 rounded-xl text-center space-y-3">
<h2 className="text-lg font-bold text-rose-450 text-rose-400">Verbindungsfehler</h2>
<p className="text-xs text-slate-400 leading-relaxed">
Die Verbindung zum lokalen Datenspeicher-Server konnte nicht hergestellt werden. Bitte vergewissern Sie sich, dass Ihr Express API-Server ordnungsgemäß läuft.
</p>
</div>
</div>
);
}
// Purely route-based render isolation structure
// 1. Administration panel
if (currentPath === "/admin" || currentPath.startsWith("/admin/")) {
return <Suspense fallback={<LoadingScreen />}><AdminPanel onLogout={handleAdminLogout} /></Suspense>;
}
// Render active portfolio
const renderActivePortfolio = () => {
const activeSuite = getActiveSuite();
const legalDocs = authorData.legalDocuments || [];
if (activeSuite === "erotica") {
return <EroticaPortfolio data={authorData.erotica} legalDocuments={legalDocs} />;
}
if (activeSuite === "clara") {
return <ClaraPortfolio data={authorData.clara} legalDocuments={legalDocs} />;
}
if (activeSuite === "renee") {
return <ReneePortfolio data={authorData.renee} legalDocuments={legalDocs} />;
}
return <SciFiPortfolio data={authorData.scifi} legalDocuments={legalDocs} />;
};
return (
<Suspense fallback={<LoadingScreen />}>
{renderActivePortfolio()}
{/* Absolute floating administration bypass link hidden on the bottom left (hover to show) */}
<div className="fixed bottom-3 left-3 opacity-0 hover:opacity-100 focus-within:opacity-100 transition-opacity duration-300 z-40 bg-neutral-950/90 border border-neutral-800 p-2 rounded-xl flex items-center text-xs font-mono shadow-xl backdrop-blur-sm">
<a
href="/admin"
onClick={(e) => {
e.preventDefault();
window.history.pushState({}, "", "/admin");
setCurrentPath("/admin");
}}
className="text-stone-500 hover:text-cyan-400 font-bold flex items-center gap-1.5 transition-colors px-1"
>
<Lock className="w-3 h-3" />
<span>Admin-Zentrale</span>
</a>
</div>
</Suspense>
);
}