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 => {