File: //tmp/bewertemich-AppLayout-lazy.jsx
import React, { Suspense, lazy, useEffect, useState } from 'react';
import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom';
import { Matomo } from './Matomo.jsx';
import { Adsense } from './Adsense.jsx';
import { apiFetch } from './api.js';
import { featuredSeoLinks, seoHub } from '../seo/catalog.js';
import { buildUploadPath } from '../../utils/tracking.js';
const lazyNamed = (loader, exportName) => lazy(() => loader().then((module) => ({ default: module[exportName] })));
const AuthModal = lazyNamed(() => import('../auth/AuthModal.jsx'), 'AuthModal');
const ChatWidget = lazyNamed(() => import('../chat/ChatWidget.jsx'), 'ChatWidget');
export const AppLayout = () => {
const location = useLocation();
const navigate = useNavigate();
const [me, setMe] = useState(null);
const [authOpen, setAuthOpen] = useState(false);
const [chatReady, setChatReady] = useState(false);
useEffect(() => {
let cancelled = false;
import('preline')
.then(() => {
if (!cancelled) {
try { window.HSStaticMethods?.autoInit?.(); } catch {}
}
})
.catch(() => {});
return () => { cancelled = true; };
}, [location.pathname]);
useEffect(() => {
const schedule = window.requestIdleCallback || ((callback) => window.setTimeout(callback, 1200));
const cancel = window.cancelIdleCallback || window.clearTimeout;
const handle = schedule(() => setChatReady(true));
return () => cancel(handle);
}, []);
useEffect(() => {
let cancelled = false;
const loadMe = async () => {
try {
const res = await apiFetch('/api/auth/me');
if (!cancelled && res.ok) {
const data = await res.json().catch(() => ({}));
setMe(data.user || null);
} else if (!cancelled) {
setMe(null);
}
} catch {
if (!cancelled) setMe(null);
}
};
loadMe();
const onAuthChanged = () => loadMe();
const onAuthOpen = () => setAuthOpen(true);
window.addEventListener('auth:changed', onAuthChanged);
window.addEventListener('auth:open', onAuthOpen);
return () => {
cancelled = true;
window.removeEventListener('auth:changed', onAuthChanged);
window.removeEventListener('auth:open', onAuthOpen);
};
}, [location.pathname]);
const onLogout = async () => {
try {
await apiFetch('/api/auth/logout', { method: 'POST' });
} finally {
try { window.dispatchEvent(new Event('auth:changed')); } catch {}
navigate('/');
}
};
return (
<div className="min-h-screen flex flex-col">
<Matomo />
<Adsense />
<header className="sticky top-0 z-40 bg-white/80 backdrop-blur border-b">
<div className="mx-auto max-w-6xl px-4 py-3 flex items-center justify-between">
<Link to="/" className="text-2xl font-black tracking-tight text-coral-500" aria-label="bewertemich.de Startseite">
bewertemich.de
</Link>
<nav className="flex items-center gap-4 text-sm">
<Link to={buildUploadPath('header_nav')} className="btn border-0 bg-coral-500 text-white hover:bg-coral-600">Upload</Link>
<Link to="/top10" className="btn btn-ghost hidden sm:inline-flex">Top 10</Link>
<a href={`/${seoHub.slug}/`} className="btn btn-ghost hidden md:inline-flex">Ratgeber</a>
{me && <Link to="/messages" className="btn btn-ghost">Nachrichten</Link>}
{me && <Link to="/wallet" className="btn btn-ghost">Credits</Link>}
{me?.is_admin ? (<Link to="/admin" className="btn btn-ghost">Admin</Link>) : null}
{!me ? (
<button className="btn btn-ghost hidden md:inline-flex" onClick={() => setAuthOpen(true)}>Login / Registrieren</button>
) : (
<div className="dropdown dropdown-end">
<div tabIndex={0} role="button" className="btn btn-ghost btn-circle avatar">
<div className="w-9 rounded-full">
{me?.avatar_photo_id ? (
<img alt="Avatar" src={`/api/photos/file/${me.avatar_photo_id}`} />
) : (
<div className="w-full h-full bg-gray-200 flex items-center justify-center text-xs font-semibold rounded-full">{(me.username||'?').slice(0,2).toUpperCase()}</div>
)}
</div>
</div>
<ul tabIndex={0} className="menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow">
<li><Link to={`/u/${me.username}`}>Profil ansehen</Link></li>
<li><Link to="/settings">Einstellungen</Link></li>
<li><button onClick={onLogout}>Abmelden</button></li>
</ul>
</div>
)}
</nav>
</div>
</header>
<main className="flex-1">
<Outlet />
</main>
<footer className="border-t py-10 text-sm text-gray-600">
<div className="mx-auto max-w-6xl px-4 flex flex-wrap items-center justify-between gap-4">
<p>© {new Date().getFullYear()} bewertemich.de</p>
<nav className="flex items-center gap-4 flex-wrap">
<a href={`/${seoHub.slug}/`} className="link">#profilbild ratgeber</a>
{featuredSeoLinks.map((item) => (
<a key={item.slug} href={`/${item.slug}/`} className="link">#{item.slug.replace(/-/g, ' ')}</a>
))}
<a href="https://panomity.de/rechtliches/impressum/" target="_blank" rel="noreferrer">Impressum</a>
<a href="https://panomity.de/rechtliches/datenschutz/" target="_blank" rel="noreferrer">Datenschutz</a>
</nav>
</div>
</footer>
{authOpen ? (
<Suspense fallback={null}>
<AuthModal open={authOpen} onClose={() => setAuthOpen(false)} onSuccess={() => setAuthOpen(false)} />
</Suspense>
) : null}
{chatReady ? (
<Suspense fallback={null}>
<ChatWidget />
</Suspense>
) : null}
</div>
);
};