DGSVO Compliance update 1 #30

Merged
Lars merged 48 commits from develop into main 2026-05-12 06:34:15 +02:00
6 changed files with 135 additions and 2 deletions
Showing only changes of commit 8261fa4420 - Show all commits

View File

@ -1,6 +1,6 @@
# Shinkan Jinkendo Version Information # Shinkan Jinkendo Version Information
APP_VERSION = "0.8.69" APP_VERSION = "0.8.70"
BUILD_DATE = "2026-05-10" BUILD_DATE = "2026-05-10"
DB_SCHEMA_VERSION = "20260508049" DB_SCHEMA_VERSION = "20260508049"
@ -29,6 +29,13 @@ MODULE_VERSIONS = {
} }
CHANGELOG = [ CHANGELOG = [
{
"version": "0.8.70",
"date": "2026-05-10",
"changes": [
"Compliance P-01b: Einstellungen/Rechtliches (/settings/legal) fuer mobile/PWA-Darstellung; Hub mit Links zu Impressum, Datenschutz, Nutzungsbedingungen, Medienrichtlinie",
],
},
{ {
"version": "0.8.69", "version": "0.8.69",
"date": "2026-05-10", "date": "2026-05-10",

View File

@ -38,6 +38,7 @@ import AdminHomeRedirect from './components/AdminHomeRedirect'
import PlatformAdminRoute from './components/PlatformAdminRoute' import PlatformAdminRoute from './components/PlatformAdminRoute'
import MediaLibraryPage from './pages/MediaLibraryPage' import MediaLibraryPage from './pages/MediaLibraryPage'
import LegalPage from './pages/LegalPage' import LegalPage from './pages/LegalPage'
import SettingsLegalPage from './pages/SettingsLegalPage'
import ActiveClubSwitcher from './components/ActiveClubSwitcher' import ActiveClubSwitcher from './components/ActiveClubSwitcher'
import InactiveMembershipBanner from './components/InactiveMembershipBanner' import InactiveMembershipBanner from './components/InactiveMembershipBanner'
import './app.css' import './app.css'
@ -183,6 +184,7 @@ function AppRoutes() {
<Route path="profile" element={<Navigate to="/settings" replace />} /> <Route path="profile" element={<Navigate to="/settings" replace />} />
<Route path="settings" element={<AccountSettingsPage />} /> <Route path="settings" element={<AccountSettingsPage />} />
<Route path="settings/system" element={<SettingsSystemInfoPage />} /> <Route path="settings/system" element={<SettingsSystemInfoPage />} />
<Route path="settings/legal" element={<SettingsLegalPage />} />
<Route path="media" element={<MediaLibraryPage />} /> <Route path="media" element={<MediaLibraryPage />} />
<Route path="exercises"> <Route path="exercises">
<Route index element={<ExercisesListPage />} /> <Route index element={<ExercisesListPage />} />

View File

@ -426,6 +426,10 @@ function AccountSettingsPage() {
<Link to="/settings/system">Technische Systeminformationen</Link> <Link to="/settings/system">Technische Systeminformationen</Link>
{' — App-Version, Build, Umgebung, Datenbankschema'} {' — App-Version, Build, Umgebung, Datenbankschema'}
</p> </p>
<p className="muted" style={{ marginTop: '0.6rem', fontSize: '0.875rem', lineHeight: 1.5 }}>
<Link to="/settings/legal">Rechtliches</Link>
{' — Impressum, Datenschutz, Nutzungsbedingungen, Medienrichtlinie'}
</p>
</div> </div>
) )
} }

View File

@ -0,0 +1,66 @@
import { Link } from 'react-router-dom'
import { Scale } from 'lucide-react'
const LEGAL_LINKS = [
{ to: '/impressum', label: 'Impressum', description: 'Angaben zum Betreiber und Verantwortlichen' },
{ to: '/datenschutz', label: 'Datenschutzerklärung', description: 'Verarbeitung personenbezogener Daten' },
{ to: '/nutzungsbedingungen', label: 'Nutzungsbedingungen', description: 'Regeln für die Nutzung der Plattform' },
{ to: '/medienrichtlinie', label: 'Medienrichtlinie', description: 'Urheberrecht, Rechte am eigenen Bild, Sichtbarkeit' },
]
function SettingsLegalPage() {
return (
<div className="page-padding app-page" style={{ padding: '1rem' }}>
<p style={{ marginBottom: '0.75rem' }}>
<Link to="/settings" style={{ fontSize: '0.9rem' }}>
Zurück zu Einstellungen
</Link>
</p>
<h1 style={{ marginBottom: '0.35rem', fontSize: '1.5rem' }}>Rechtliches</h1>
<p
style={{
color: 'var(--text2)',
marginBottom: '1.25rem',
fontSize: '0.95rem',
lineHeight: 1.5,
maxWidth: '40rem',
}}
>
Rechtstexte und Richtlinien der Plattform.
Die Inhalte befinden sich noch in redaktioneller Prüfung.
</p>
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
{LEGAL_LINKS.map((item, idx) => (
<Link
key={item.to}
to={item.to}
style={{
display: 'flex',
alignItems: 'center',
gap: '0.85rem',
padding: '1rem 1.1rem',
borderBottom: idx < LEGAL_LINKS.length - 1 ? '1px solid var(--border)' : 'none',
textDecoration: 'none',
color: 'inherit',
}}
>
<Scale size={18} style={{ color: 'var(--text3)', flexShrink: 0 }} />
<div>
<div style={{ fontWeight: 500, color: 'var(--text1)', fontSize: '0.95rem' }}>
{item.label}
</div>
<div style={{ fontSize: '0.8rem', color: 'var(--text3)', marginTop: '0.15rem' }}>
{item.description}
</div>
</div>
<span style={{ marginLeft: 'auto', color: 'var(--text3)', fontSize: '1.1rem' }}></span>
</Link>
))}
</div>
</div>
)
}
export default SettingsLegalPage

View File

@ -1,11 +1,12 @@
// Shinkan Jinkendo Frontend Version // Shinkan Jinkendo Frontend Version
export const APP_VERSION = "0.8.69" export const APP_VERSION = "0.8.70"
export const BUILD_DATE = "2026-05-10" export const BUILD_DATE = "2026-05-10"
export const PAGE_VERSIONS = { export const PAGE_VERSIONS = {
LoginPage: "1.0.2", LoginPage: "1.0.2",
LegalPage: "1.0.0", LegalPage: "1.0.0",
SettingsLegalPage: "1.0.0",
Dashboard: "1.0.0", Dashboard: "1.0.0",
AccountSettingsPage: "1.0.1", AccountSettingsPage: "1.0.1",
ExercisesPage: "1.5.0", // Fokus +/- Regeln, nur ohne Fokusbereich; Filterprefs ExercisesPage: "1.5.0", // Fokus +/- Regeln, nur ohne Fokusbereich; Filterprefs

View File

@ -230,6 +230,59 @@ test('P-01: Login-Seite enthält Links zu allen vier Rechtstextseiten', async ({
console.log('✓ P-01: Login-Seite alle vier Rechtstext-Links vorhanden'); console.log('✓ P-01: Login-Seite alle vier Rechtstext-Links vorhanden');
}); });
// P-01b: Rechtliches über Einstellungen (Mobile/PWA-Erreichbarkeit)
test('P-01b: Einstellungen enthält Link zu Rechtliches', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await login(page);
await page.waitForLoadState('networkidle');
await page.goto('/settings');
await page.waitForLoadState('networkidle');
const link = page.locator('a[href="/settings/legal"]');
await expect(link).toBeVisible();
console.log('✓ P-01b: Einstellungen enthält Link zu /settings/legal');
});
test('P-01b: /settings/legal enthält Links zu allen vier Rechtstextseiten', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await login(page);
await page.waitForLoadState('networkidle');
await page.goto('/settings/legal');
await page.waitForLoadState('networkidle');
await expect(page.getByRole('heading', { level: 1 })).toContainText('Rechtliches');
for (const route of LEGAL_ROUTES) {
const link = page.locator(`a[href="${route.path}"]`);
await expect(link).toBeVisible();
}
console.log('✓ P-01b: /settings/legal Überschrift + alle vier Rechtstext-Links vorhanden');
});
test('P-01b: Jeder Rechtstext-Link aus /settings/legal führt zur korrekten Route', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await login(page);
await page.waitForLoadState('networkidle');
for (const route of LEGAL_ROUTES) {
await page.goto('/settings/legal');
await page.waitForLoadState('networkidle');
await page.locator(`a[href="${route.path}"]`).click();
await page.waitForLoadState('networkidle');
expect(page.url()).toContain(route.path);
await expect(page.getByRole('heading', { level: 1 })).toContainText(route.label);
console.log(`✓ P-01b: ${route.label} Link aus /settings/legal korrekt`);
}
});
test('8. Keine kritischen Console-Fehler', async ({ page }) => { test('8. Keine kritischen Console-Fehler', async ({ page }) => {
const errors = []; const errors = [];
page.on('console', msg => { page.on('console', msg => {