import type { Metadata } from "next"; import { redirect } from "next/navigation"; import { noticeUrl } from "@/lib/notice"; import { getCurrentAdmin } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; import { logoutAction } from "@/app/konto/actions"; import { AdminShell } from "@/components/admin-shell"; import { getLocale } from "@/i18n/locale"; import { getDictionary } from "@/i18n/get-dictionary"; import { getBrandAssets } from "@/lib/brand"; // Admin-Daten (Bestellungen, Bestand) muessen immer aktuell sein; zudem // entkoppelt das den Docker-Build-Schritt von einer DB-Verbindung. export const dynamic = "force-dynamic"; // Einzelne Admin-Unterseiten (bestellungen, produkte, ...) haben bewusst // keine eigene Metadata — der Bereich ist ueber robots.ts von der Indexierung // ausgeschlossen. Dieses Template sorgt trotzdem fuer einen sinnvollen // Browser-Tab-Titel statt des generischen Startseiten-Titels ueberall im // Admin-Bereich, ohne jede Unterseite einzeln pflegen zu muessen. export async function generateMetadata(): Promise { const t = getDictionary(await getLocale()); return { title: { default: t.account.navAdmin.title, template: `%s | ${t.account.navAdmin.title}` }, robots: { index: false, follow: false }, }; } export default async function AdminLayout({ children }: LayoutProps<"/admin">) { // Der Proxy (src/proxy.ts) blockt fremde Requests bereits vor dem Rendern; // diese zweite, gegen die Datenbank autoritative Pruefung faengt u.a. den // Fall ab, dass die ADMIN-Rolle eines eingeloggten Kontos nach dem Login // entzogen wurde (das JWT traegt die Rolle nur als Snapshot vom Login-Zeitpunkt). const admin = await getCurrentAdmin(); if (!admin) { redirect(noticeUrl("admin-login")); } // Erzwungen statt nur empfohlen: Admin-Konten haben Zugriff auf // Bestellungen, Rueckerstattungen und Kundendaten — ohne 2FA waere ein // gestohlenes Passwort allein genug fuer vollen Zugriff. Weiterleitung // statt Blockade, damit das Konto die Einrichtung direkt nachholen kann. if (!admin.twoFactorEnabled) { redirect(noticeUrl("admin-2fa")); } // Was gerade auf Bearbeitung wartet — erscheint als Zaehler in der // Seitenleiste. Nur echte Arbeit: bezahlte, noch nicht versendete oder // abgeholte Bestellungen (keine offenen Zahlungen), neue Retouren, // unfreigegebene Bewertungen, neue Firmenanfragen. Reine COUNT-Abfragen. const brand = await getBrandAssets(); const [openOrders, openReturns, pendingReviews, newInquiries] = await Promise.all([ prisma.order.count({ where: { status: { in: ["BEZAHLT", "IN_VORBEREITUNG", "VERSANDBEREIT", "ABHOLBEREIT"] }, archivedAt: null }, }), prisma.returnRequest.count({ where: { status: "ANGEFRAGT" } }), prisma.review.count({ where: { isApproved: false } }), prisma.businessInquiry.count({ where: { status: "NEU" } }), ]); return ( {children} ); }