import type { Metadata } from "next" import Link from "next/link" import { notFound } from "next/navigation" import { PrismaClient } from "@prisma/client" import { ArrowLeft, Calendar } from "lucide-react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { ObjectionNotice } from "@/components/objection-notice" import { PublicHeader } from "@/components/public-header" import { SiteFooter } from "@/components/site-footer" import { PrintPolicy } from "@/components/print-policy" import { readPolicyDefaults, toApiPolicy } from "@/lib/policy-defaults.server" import { markdownToHtml } from "@/lib/markdown" import { formatDate } from "@/lib/format" import { PrivacyPolicy } from "@/types/privacy-policy" // Prisma client instance const prisma = new PrismaClient() // Inhalt kommt aus der Datenbank und ändert sich mit jeder Pflege im Admin. export const dynamic = "force-dynamic" /** * Lädt eine veröffentlichte Erklärung samt Standardtexten. * * Entwürfe (`isPublic = false`) verhalten sich hier wie nicht vorhanden – die * Seite ist ohne Anmeldung erreichbar und darf sie nicht preisgeben. */ async function loadPolicy(slug: string) { const [policy, defaults] = await Promise.all([ prisma.privacyPolicy.findUnique({ where: { slug } }), readPolicyDefaults(prisma), ]) if (!policy || !policy.isPublic) return null return toApiPolicy(policy, defaults) } export async function generateMetadata({ params, }: { params: Promise<{ slug: string }> }): Promise { const { slug } = await params const policy = await loadPolicy(slug) if (!policy) return { title: "Nicht gefunden" } return { title: policy.title, description: `Datenschutzerklärung nach DSGVO: ${policy.title}`, } } /** Ein Abschnitt der Erklärung; leere Abschnitte entfallen ganz. */ async function Section({ title, content, }: { title: string content?: string | null }) { if (!content?.trim()) return null const html = await markdownToHtml(content) return (

{title}

) } /** Die Abschnitte in der Reihenfolge, in der sie gelesen werden sollen. */ function sectionsOf(policy: PrivacyPolicy) { return [ { heading: "Verantwortlich für die Verarbeitung", items: [ ["Verantwortlicher im Sinne der DSGVO", policy.responsible], ["Kontaktdaten der/des Datenschutzbeauftragten", policy.contactDPO], ] as const, }, { heading: "Die Verarbeitung", items: [ ["Bezeichnung der Verarbeitungstätigkeit", policy.processingDescription], ["Zweck der Datenerhebung", policy.processingPurposes], ["Rechtsgrundlage der Verarbeitung", policy.legalBasis], ["Berechtigte Interessen", policy.legitimateInterests], ["Kategorien der verarbeiteten Daten", policy.dataCategories], ["Geplante Empfänger der Daten", policy.recipients], ["Übermittlung in Drittländer", policy.thirdCountryTransfer], ["Speicherdauer", policy.storageDuration], ["Herkunft der Daten", policy.dataSource], ["Automatisierte Entscheidungsfindung", policy.automatedDecision], ] as const, }, { heading: "Ihre Rechte", items: [ ["Betroffenen-Rechte", policy.dataSubjectsRights], ["Beschwerderecht bei der Aufsichtsbehörde", policy.complaintRight], ["Widerrufsrecht bei Einwilligung", policy.withdrawalRight], ["Ihre Pflicht zur Bereitstellung der Daten", policy.provisionObligation], ["Folgen, wenn Sie die Daten nicht angeben", policy.provisionConsequences], ] as const, }, ] } export default async function PolicyPage({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params const policy = await loadPolicy(slug) if (!policy) notFound() return (

{policy.title}

Erstellt am {formatDate(policy.createdAt)} {policy.updatedAt !== policy.createdAt && ( Aktualisiert am {formatDate(policy.updatedAt)} )} DSGVO
{/* Art. 21 Abs. 4 verlangt den Hinweis getrennt von den übrigen Informationen – deshalb steht er vor den Abschnitten in einem eigenen, abgesetzten Kasten. */}
{sectionsOf(policy).map((group) => (

{group.heading}

{group.items.map(([title, content]) => (
))}
))}
) }