From d7ed0c0e9b3ce17c56e3f89a2fe89d99f17fc707 Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 10 May 2026 09:41:45 +0200 Subject: [PATCH] feat(compliance): P-01 Rechtstextseiten technisch anlegen (0.8.69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Öffentliche Routen /impressum /datenschutz /nutzungsbedingungen /medienrichtlinie ohne Auth erreichbar. LegalPage-Komponente mit deutlichem Platzhalterhinweis und strukturierten Pflichtfeldern je Rechtstext. Links in LoginPage-Footer und DesktopSidebar-Footer. KRIT-01 technischer Teil geschlossen. Juristische Inhalte bleiben offen — Betreiber + Rechtsanwalt erforderlich. - frontend/src/pages/LegalPage.jsx (neu) - frontend/src/App.jsx: 4 öffentliche Routen - frontend/src/pages/LoginPage.jsx: Rechtstext-Links im Footer - frontend/src/components/DesktopSidebar.jsx: Links im Sidebar-Footer - tests/dev-smoke-test.spec.js: 5 neue P-01-Tests version: 0.8.69 (backend + frontend) Co-Authored-By: Claude Sonnet 4.6 --- backend/version.py | 10 +- frontend/src/App.jsx | 7 + frontend/src/components/DesktopSidebar.jsx | 16 +- frontend/src/pages/LegalPage.jsx | 212 +++++++++++++++++++++ frontend/src/pages/LoginPage.jsx | 21 +- frontend/src/version.js | 5 +- tests/dev-smoke-test.spec.js | 46 +++++ 7 files changed, 312 insertions(+), 5 deletions(-) create mode 100644 frontend/src/pages/LegalPage.jsx diff --git a/backend/version.py b/backend/version.py index c0df55b..ac2ca16 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,6 +1,6 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.68" +APP_VERSION = "0.8.69" BUILD_DATE = "2026-05-10" DB_SCHEMA_VERSION = "20260508049" @@ -29,6 +29,14 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.69", + "date": "2026-05-10", + "changes": [ + "Compliance P-01 (KRIT-01) technischer Teil: Rechtstextseiten /impressum, /datenschutz, /nutzungsbedingungen, /medienrichtlinie als oeffentliche Routen angelegt (Platzhalter, kein Auth erforderlich)", + "Login-Seite und Desktop-Sidebar enthalten Links zu allen vier Rechtstextseiten", + ], + }, { "version": "0.8.68", "date": "2026-05-10", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index db88ff6..6067359 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -37,6 +37,7 @@ import AdminUsersPage from './pages/AdminUsersPage' import AdminHomeRedirect from './components/AdminHomeRedirect' import PlatformAdminRoute from './components/PlatformAdminRoute' import MediaLibraryPage from './pages/MediaLibraryPage' +import LegalPage from './pages/LegalPage' import ActiveClubSwitcher from './components/ActiveClubSwitcher' import InactiveMembershipBanner from './components/InactiveMembershipBanner' import './app.css' @@ -171,6 +172,12 @@ function AppRoutes() { } /> + {/* P-01: Öffentliche Rechtstextseiten — kein Auth erforderlich */} + } /> + } /> + } /> + } /> + }> } /> } /> diff --git a/frontend/src/components/DesktopSidebar.jsx b/frontend/src/components/DesktopSidebar.jsx index a4af493..f5f582a 100644 --- a/frontend/src/components/DesktopSidebar.jsx +++ b/frontend/src/components/DesktopSidebar.jsx @@ -1,4 +1,4 @@ -import { NavLink, useLocation } from 'react-router-dom' +import { NavLink, Link, useLocation } from 'react-router-dom' import { LogOut } from 'lucide-react' import { getMainNavItems } from '../config/appNav' import { useOrgInbox } from '../context/OrgInboxContext' @@ -92,6 +92,20 @@ export default function DesktopSidebar({ +
+ Impressum + Datenschutz + Nutzungsbedingungen + Medienrichtlinie +
) } diff --git a/frontend/src/pages/LegalPage.jsx b/frontend/src/pages/LegalPage.jsx new file mode 100644 index 0000000..349af9e --- /dev/null +++ b/frontend/src/pages/LegalPage.jsx @@ -0,0 +1,212 @@ +import { Link } from 'react-router-dom' + +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 page = PAGES[type] + if (!page) return null + + return ( +
+
+ +
+ + ← Zurück zur Anmeldung + +
+ +
+ ⚠ MUSTER / PLATZHALTER +

+ Inhalt wird vor Produktivbetrieb juristisch geprüft und durch den Betreiber ergänzt. + Diese Seite hat keinen rechtlich verbindlichen Charakter. +

+
+ +

{page.title}

+ + {page.sections.map((section) => ( +
+

+ {section.heading} +

+

+ {section.placeholder} +

+
+ ))} + +
+ {LEGAL_LINKS.map((l) => ( + + {l.label} + + ))} +
+
+
+ ) +} + +export default LegalPage diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index 06d6914..60a97e8 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react' -import { useNavigate } from 'react-router-dom' +import { useNavigate, Link } from 'react-router-dom' import { useAuth } from '../context/AuthContext' import api from '../utils/api' @@ -238,6 +238,25 @@ function LoginPage() {

)} +
+ Impressum + Datenschutz + Nutzungsbedingungen + Medienrichtlinie +
) diff --git a/frontend/src/version.js b/frontend/src/version.js index a60a1b5..31ceb7d 100644 --- a/frontend/src/version.js +++ b/frontend/src/version.js @@ -1,10 +1,11 @@ // Shinkan Jinkendo Frontend Version -export const APP_VERSION = "0.8.68" +export const APP_VERSION = "0.8.69" export const BUILD_DATE = "2026-05-10" export const PAGE_VERSIONS = { - LoginPage: "1.0.1", + LoginPage: "1.0.2", + LegalPage: "1.0.0", Dashboard: "1.0.0", AccountSettingsPage: "1.0.1", ExercisesPage: "1.5.0", // Fokus +/- Regeln, nur ohne Fokusbereich; Filterprefs diff --git a/tests/dev-smoke-test.spec.js b/tests/dev-smoke-test.spec.js index 923d2d2..8a61fb7 100644 --- a/tests/dev-smoke-test.spec.js +++ b/tests/dev-smoke-test.spec.js @@ -184,6 +184,52 @@ test('P-12: sessionStorage wird bei Logout bereinigt (sj_coach_* Schlüssel)', a console.log('✓ P-12: sj_coach_* entfernt, Fremd-Key erhalten, authToken entfernt'); }); +// P-01: Rechtstextseiten – öffentliche Routen ohne Auth + +const LEGAL_ROUTES = [ + { path: '/impressum', label: 'Impressum' }, + { path: '/datenschutz', label: 'Datenschutz' }, + { path: '/nutzungsbedingungen', label: 'Nutzungsbedingungen' }, + { path: '/medienrichtlinie', label: 'Medienrichtlinie' }, +]; + +for (const route of LEGAL_ROUTES) { + test(`P-01: ${route.label} ohne Auth erreichbar und enthält Platzhalterhinweis`, async ({ page }) => { + // Direkt aufrufen ohne Login + await page.goto(route.path); + await page.waitForLoadState('networkidle'); + + // Seite ist erreichbar (kein Redirect zur Login-Seite) + expect(page.url()).toContain(route.path); + + // Platzhalterhinweis sichtbar + const hinweis = await page.getByText('MUSTER / PLATZHALTER').first(); + await expect(hinweis).toBeVisible(); + + // Seitentitel korrekt + await expect(page.getByRole('heading', { level: 1 })).toContainText(route.label); + + // Reload funktioniert + await page.reload(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toContain(route.path); + + console.log(`✓ P-01: ${route.label} – ohne Auth erreichbar, Platzhalter sichtbar, Reload OK`); + }); +} + +test('P-01: Login-Seite enthält Links zu allen vier Rechtstextseiten', async ({ page }) => { + await page.goto('/login'); + await page.waitForLoadState('networkidle'); + + for (const route of LEGAL_ROUTES) { + const link = page.locator(`a[href="${route.path}"]`); + await expect(link).toBeVisible(); + } + + console.log('✓ P-01: Login-Seite – alle vier Rechtstext-Links vorhanden'); +}); + test('8. Keine kritischen Console-Fehler', async ({ page }) => { const errors = []; page.on('console', msg => {