feat(compliance): P-01 Rechtstextseiten technisch anlegen (0.8.69)
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 32s
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 32s
Ö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 <noreply@anthropic.com>
This commit is contained in:
parent
d73ed13f87
commit
d7ed0c0e9b
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
<Route path="/impressum" element={<LegalPage type="impressum" />} />
|
||||
<Route path="/datenschutz" element={<LegalPage type="datenschutz" />} />
|
||||
<Route path="/nutzungsbedingungen" element={<LegalPage type="nutzungsbedingungen" />} />
|
||||
<Route path="/medienrichtlinie" element={<LegalPage type="medienrichtlinie" />} />
|
||||
|
||||
<Route element={<ProtectedLayout />}>
|
||||
<Route index element={<Dashboard />} />
|
||||
<Route path="profile" element={<Navigate to="/settings" replace />} />
|
||||
|
|
|
|||
|
|
@ -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({
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
padding: '0.5rem 1rem 0.75rem',
|
||||
display: 'flex',
|
||||
gap: '0.6rem',
|
||||
flexWrap: 'wrap',
|
||||
fontSize: '0.7rem',
|
||||
}}
|
||||
>
|
||||
<Link to="/impressum" style={{ color: 'var(--text3)' }}>Impressum</Link>
|
||||
<Link to="/datenschutz" style={{ color: 'var(--text3)' }}>Datenschutz</Link>
|
||||
<Link to="/nutzungsbedingungen" style={{ color: 'var(--text3)' }}>Nutzungsbedingungen</Link>
|
||||
<Link to="/medienrichtlinie" style={{ color: 'var(--text3)' }}>Medienrichtlinie</Link>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
212
frontend/src/pages/LegalPage.jsx
Normal file
212
frontend/src/pages/LegalPage.jsx
Normal file
|
|
@ -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 (
|
||||
<div style={{ minHeight: '100vh', background: 'var(--bg)', padding: '2rem 1rem' }}>
|
||||
<div style={{ maxWidth: '720px', margin: '0 auto' }}>
|
||||
|
||||
<div style={{ marginBottom: '1.5rem' }}>
|
||||
<Link to="/login" style={{ color: 'var(--accent)', textDecoration: 'none', fontSize: '0.9rem' }}>
|
||||
← Zurück zur Anmeldung
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="card"
|
||||
style={{
|
||||
marginBottom: '1.5rem',
|
||||
borderLeft: '4px solid var(--danger)',
|
||||
background: 'var(--surface)',
|
||||
}}
|
||||
>
|
||||
<strong style={{ color: 'var(--danger)' }}>⚠ MUSTER / PLATZHALTER</strong>
|
||||
<p style={{ margin: '0.5rem 0 0', color: 'var(--text2)', fontSize: '0.9rem' }}>
|
||||
Inhalt wird vor Produktivbetrieb juristisch geprüft und durch den Betreiber ergänzt.
|
||||
Diese Seite hat keinen rechtlich verbindlichen Charakter.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h1 style={{ marginBottom: '2rem', color: 'var(--text1)' }}>{page.title}</h1>
|
||||
|
||||
{page.sections.map((section) => (
|
||||
<div key={section.heading} style={{ marginBottom: '1.75rem' }}>
|
||||
<h2 style={{ fontSize: '1.05rem', marginBottom: '0.4rem', color: 'var(--text1)' }}>
|
||||
{section.heading}
|
||||
</h2>
|
||||
<p style={{ color: 'var(--text3)', fontStyle: 'italic', margin: 0 }}>
|
||||
{section.placeholder}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: '3rem',
|
||||
paddingTop: '1rem',
|
||||
borderTop: '1px solid var(--border)',
|
||||
fontSize: '0.82rem',
|
||||
color: 'var(--text3)',
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
gap: '1.25rem',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
{LEGAL_LINKS.map((l) => (
|
||||
<Link key={l.to} to={l.to} style={{ color: 'var(--text3)' }}>
|
||||
{l.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LegalPage
|
||||
|
|
@ -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() {
|
|||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
marginTop: '1.5rem',
|
||||
paddingTop: '1rem',
|
||||
borderTop: '1px solid var(--border)',
|
||||
fontSize: '0.78rem',
|
||||
color: 'var(--text3)',
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
gap: '1rem',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
<Link to="/impressum" style={{ color: 'var(--text3)' }}>Impressum</Link>
|
||||
<Link to="/datenschutz" style={{ color: 'var(--text3)' }}>Datenschutz</Link>
|
||||
<Link to="/nutzungsbedingungen" style={{ color: 'var(--text3)' }}>Nutzungsbedingungen</Link>
|
||||
<Link to="/medienrichtlinie" style={{ color: 'var(--text3)' }}>Medienrichtlinie</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user