import { useState, useEffect } from 'react' import { Link } from 'react-router-dom' import { jsPDF } from 'jspdf' import api from '../utils/api' function generateLegalPdf(doc) { const pdf = new jsPDF({ format: 'a4', unit: 'mm' }) const marginL = 22, marginR = 22, marginTop = 28, pageW = 210 const contentW = pageW - marginL - marginR const bottomLimit = 277 let y = marginTop const checkBreak = (need) => { if (y + need > bottomLimit) { pdf.addPage(); y = marginTop } } pdf.setFont('helvetica', 'bold') pdf.setFontSize(20) pdf.text(doc.title, marginL, y) y += 10 const dateStr = doc.published_at ? new Date(doc.published_at).toLocaleDateString('de-DE') : new Date(doc.updated_at || doc.created_at).toLocaleDateString('de-DE') const metaLine = `Version ${doc.version} | Gueltig seit ${dateStr}` pdf.setFont('helvetica', 'normal') pdf.setFontSize(10) pdf.setTextColor(90, 90, 90) pdf.text(metaLine, marginL, y) y += 3 pdf.setDrawColor(0, 0, 0) pdf.setLineWidth(0.4) pdf.line(marginL, y, pageW - marginR, y) y += 8 pdf.setTextColor(0, 0, 0) for (const section of (doc.content_sections || [])) { checkBreak(14) pdf.setFont('helvetica', 'bold') pdf.setFontSize(11) pdf.text(section.heading || '', marginL, y) y += 6 if (section.content) { pdf.setFont('helvetica', 'normal') pdf.setFontSize(10) const lines = pdf.splitTextToSize(section.content, contentW) for (const line of lines) { checkBreak(5) pdf.text(line, marginL, y) y += 5 } } y += 5 } const total = pdf.getNumberOfPages() for (let i = 1; i <= total; i++) { pdf.setPage(i) pdf.setFont('helvetica', 'normal') pdf.setFontSize(8) pdf.setTextColor(150, 150, 150) const fy = 289 pdf.text( `Shinkan Jinkendo | Exportiert am ${new Date().toLocaleDateString('de-DE')}`, marginL, fy ) pdf.text(`Seite ${i} von ${total}`, pageW - marginR, fy, { align: 'right' }) } pdf.save(`${doc.document_type}_v${doc.version}.pdf`) } // document_type values used in the DB / API const TYPE_MAP = { impressum: 'impressum', datenschutz: 'privacy_policy', nutzungsbedingungen: 'terms_of_use', medienrichtlinie: 'media_policy', } const PAGES = { impressum: { title: 'Impressum', sections: [ { heading: 'Betreiber / Verantwortlicher', placeholder: '[Name und Rechtsform des Betreibers — vom Betreiber einzutragen]', }, { heading: 'Anschrift', placeholder: '[Straße, Hausnummer, PLZ, Ort — vom Betreiber einzutragen]', }, { heading: 'Vertretungsberechtigte Person', placeholder: '[Name der vertretungsberechtigten Person — vom Betreiber einzutragen]', }, { heading: 'Kontakt', placeholder: '[E-Mail-Adresse, ggf. Telefonnummer — vom Betreiber einzutragen]', }, { heading: 'Registerangaben (falls relevant)', placeholder: '[Vereinsregister, Handelsregister o. ä. — vom Rechtsanwalt prüfen lassen]', }, ], }, datenschutz: { title: 'Datenschutzerklärung', sections: [ { heading: 'Verantwortlicher', placeholder: '[Name, Anschrift und Kontakt des Verantwortlichen — vom Betreiber einzutragen]', }, { heading: 'Zwecke der Verarbeitung', placeholder: '[Welche Daten werden zu welchem Zweck verarbeitet? — vom Rechtsanwalt zu formulieren]', }, { heading: 'Rechtsgrundlagen', placeholder: '[Art. 6 DSGVO: Einwilligung, Vertrag, berechtigtes Interesse — vom Rechtsanwalt zu bestimmen]', }, { heading: 'Empfänger und Dienstleister', placeholder: '[SMTP-Anbieter, Hosting, ggf. weitere — vom Rechtsanwalt und Betreiber zu listen]', }, { heading: 'Speicherdauern', placeholder: '[Wie lange werden welche Daten gespeichert? — vom Rechtsanwalt zu bestimmen]', }, { heading: 'Betroffenenrechte', placeholder: '[Auskunft, Berichtigung, Löschung, Widerspruch, Datenübertragbarkeit — vom Rechtsanwalt zu formulieren]', }, { heading: 'Browser-Speicher (localStorage, sessionStorage)', placeholder: '[Technisch notwendige Speicherung des Auth-Tokens und Sitzungsdaten. Nach TDDDG §25 ggf. ohne Einwilligung zulässig — vom Rechtsanwalt zu prüfen]', }, { heading: 'Kontakt für Datenschutzanfragen', placeholder: '[E-Mail oder Kontaktweg für Datenschutzanfragen — vom Betreiber einzutragen]', }, { heading: 'Zuständige Aufsichtsbehörde', placeholder: '[Zuständige Datenschutzbehörde je nach Bundesland — vom Rechtsanwalt zu bestimmen]', }, ], }, nutzungsbedingungen: { title: 'Nutzungsbedingungen', sections: [ { heading: 'Nutzungsumfang', placeholder: '[Für welche Nutzergruppen und Zwecke darf die Plattform genutzt werden? — vom Rechtsanwalt zu formulieren]', }, { heading: 'Registrierung', placeholder: '[Voraussetzungen für die Registrierung, Wahrheitspflicht der Angaben — vom Rechtsanwalt zu formulieren]', }, { heading: 'Zulässige und unzulässige Inhalte', placeholder: '[Was darf hochgeladen und veröffentlicht werden? Was ist verboten? — vom Rechtsanwalt zu formulieren]', }, { heading: 'Verantwortlichkeit der Nutzer', placeholder: '[Nutzer sind für ihre hochgeladenen Inhalte selbst verantwortlich — vom Rechtsanwalt zu formulieren]', }, { heading: 'Sperrung und Löschung', placeholder: '[Unter welchen Bedingungen können Konten oder Inhalte gesperrt oder gelöscht werden? — vom Rechtsanwalt zu formulieren]', }, { heading: 'Haftungshinweise', placeholder: '[Haftungsausschluss für Nutzerinhalte, externe Links, Systemausfälle — vom Rechtsanwalt zu formulieren]', }, { heading: 'Geltungsbereich und anwendbares Recht', placeholder: '[Welches Recht ist anwendbar? Welcher Gerichtsstand gilt? — vom Rechtsanwalt zu bestimmen]', }, ], }, medienrichtlinie: { title: 'Medienrichtlinie', sections: [ { heading: 'Urheberrechte', placeholder: '[Nur eigene oder ausdrücklich lizenzierte Inhalte hochladen — vom Rechtsanwalt zu formulieren]', }, { heading: 'Rechte am eigenen Bild (§ 22 KUG)', placeholder: '[Erkennbare Personen müssen eingewilligt haben; besondere Regeln für Minderjährige — juristisch zu prüfen und zu formulieren]', }, { heading: 'Minderjährige', placeholder: '[Besondere Schutzpflichten bei Aufnahmen von Minderjährigen — vom Rechtsanwalt zu formulieren]', }, { heading: 'Musik und sonstige Fremdinhalte', placeholder: '[Keine Hintergrundmusik oder andere Fremdinhalte ohne gültige Lizenz — vom Rechtsanwalt zu formulieren]', }, { heading: 'Sichtbarkeitsstufen', placeholder: '[Erläuterung der Stufen: privat, vereinsintern, öffentlich — vom Betreiber zu beschreiben]', }, { heading: 'Meldewege für rechtsverletzende Inhalte', placeholder: '[Wie können Inhalte gemeldet werden? — wird nach Umsetzung von P-13 (Content-Melde-Backend) ergänzt]', }, { heading: 'Lösch- und Sperrlogik', placeholder: '[Wann und wie werden gemeldete Inhalte entfernt oder gesperrt? — wird nach Umsetzung von P-11 und P-13 ergänzt]', }, ], }, } const LEGAL_LINKS = [ { to: '/impressum', label: 'Impressum' }, { to: '/datenschutz', label: 'Datenschutz' }, { to: '/nutzungsbedingungen', label: 'Nutzungsbedingungen' }, { to: '/medienrichtlinie', label: 'Medienrichtlinie' }, ] function LegalPage({ type }) { const fallback = PAGES[type] const [apiDoc, setApiDoc] = useState(undefined) // undefined = loading, null = not found const [loading, setLoading] = useState(true) const documentType = TYPE_MAP[type] useEffect(() => { if (!documentType) { setLoading(false) return } api.getPublishedLegalDocument(documentType) .then(doc => setApiDoc(doc)) .catch(() => setApiDoc(null)) .finally(() => setLoading(false)) }, [documentType]) if (!fallback) return null const isPlaceholder = !apiDoc const title = apiDoc ? apiDoc.title : fallback.title const sections = apiDoc ? (apiDoc.content_sections || []) : fallback.sections.map(s => ({ heading: s.heading, content: s.placeholder })) return (
Inhalt wird vor Produktivbetrieb juristisch geprüft und durch den Betreiber ergänzt. Diese Seite hat keinen rechtlich verbindlichen Charakter.
{section.content}