/**
* Modul: Client-Side Router
* Zweck: SPA-Routing über History API ohne Framework, Auth-Guard, Seiten-Übergänge
* Abhängigkeiten: api.js
*/
import { auth } from '/api.js';
// --------------------------------------------------------
// Routen-Definitionen
// Jede Route hat: path, page (dynamisch geladen), requiresAuth
// --------------------------------------------------------
const ROUTES = [
{ path: '/login', page: '/pages/login.js', requiresAuth: false },
{ path: '/', page: '/pages/dashboard.js', requiresAuth: true },
{ path: '/tasks', page: '/pages/tasks.js', requiresAuth: true },
{ path: '/shopping', page: '/pages/shopping.js', requiresAuth: true },
{ path: '/meals', page: '/pages/meals.js', requiresAuth: true },
{ path: '/calendar', page: '/pages/calendar.js', requiresAuth: true },
{ path: '/notes', page: '/pages/notes.js', requiresAuth: true },
{ path: '/contacts', page: '/pages/contacts.js', requiresAuth: true },
{ path: '/budget', page: '/pages/budget.js', requiresAuth: true },
{ path: '/settings', page: '/pages/settings.js', requiresAuth: true },
];
// --------------------------------------------------------
// Modul-Cache: verhindert redundante dynamic imports bei Navigation
// --------------------------------------------------------
const moduleCache = new Map();
async function importPage(pagePath) {
if (!moduleCache.has(pagePath)) {
moduleCache.set(pagePath, await import(pagePath));
}
return moduleCache.get(pagePath);
}
// --------------------------------------------------------
// Globaler App-State
// --------------------------------------------------------
let currentUser = null;
let currentPath = null;
// --------------------------------------------------------
// Router
// --------------------------------------------------------
/**
* Navigiert zu einem Pfad und rendert die entsprechende Seite.
* @param {string} path
* @param {Object|boolean} userOrPushState - Direkt ein User-Objekt nach Login,
* oder boolean (pushState) für interne Navigation
* @param {boolean} pushState - false beim initialen Load und popstate
*/
async function navigate(path, userOrPushState = true, pushState = true) {
// Überlastung: navigate(path, user) nach Login vs navigate(path, false) beim Init
if (typeof userOrPushState === 'object' && userOrPushState !== null) {
currentUser = userOrPushState;
} else {
pushState = userOrPushState;
}
currentPath = path;
const route = ROUTES.find((r) => r.path === path) ?? ROUTES.find((r) => r.path === '/');
// Auth-Guard
if (route.requiresAuth && !currentUser) {
try {
const result = await auth.me();
currentUser = result.user;
} catch {
currentPath = null; // Reset damit navigate('/login') nicht geblockt wird
navigate('/login');
return;
}
}
if (!route.requiresAuth && currentUser && path === '/login') {
currentPath = null;
navigate('/');
return;
}
if (pushState) {
history.pushState({ path }, '', path);
}
await renderPage(route);
updateNav(path);
}
/**
* Lädt und rendert eine Seite dynamisch.
* @param {{ path: string, page: string }} route
*/
async function renderPage(route) {
const app = document.getElementById('app');
const loading = document.getElementById('app-loading');
// Loading verstecken
if (loading) loading.hidden = true;
try {
const module = await importPage(route.page);
if (typeof module.render !== 'function') {
throw new Error(`Seite ${route.page} exportiert keine render()-Funktion.`);
}
// App-Shell einmalig aufbauen BEVOR render() aufgerufen wird —
// page-content muss im DOM existieren damit document.getElementById()
// in Seiten-Modulen funktioniert.
if (!document.querySelector('.nav-bottom') && currentUser) {
renderAppShell(app);
}
const content = document.getElementById('page-content') || app;
// Alte Seite kurz ausfaden, falls vorhanden
const oldPage = content.querySelector('.page-transition');
if (oldPage) {
oldPage.classList.add('page-transition--out');
await new Promise(r => setTimeout(r, 120));
}
// Seiten-Wrapper bereits jetzt in den DOM einfügen, damit
// document.getElementById() in render() die richtigen Elemente findet.
const pageWrapper = document.createElement('div');
pageWrapper.className = 'page-transition';
pageWrapper.style.animation = 'page-in 0.2s ease forwards';
content.replaceChildren(pageWrapper);
await module.render(pageWrapper, { user: currentUser });
} catch (err) {
console.error('[Router] Seiten-Render-Fehler:', err);
renderError(app, err);
}
}
/**
* App-Shell mit Navigation einmalig aufbauen (nach erstem Login).
*/
function renderAppShell(container) {
container.innerHTML = `
Zum Inhalt springen