diff --git a/src/App.jsx b/src/App.jsx index 6fa9ad9..eb69455 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { Routes, Route, Navigate } from 'react-router-dom'; +import { Routes, Route, Navigate, useLocation } from 'react-router-dom'; import { useAuth } from './contexts/AuthContext'; import { useLanguage } from './contexts/LanguageContext'; import { useBranding } from './contexts/BrandingContext'; @@ -24,8 +24,9 @@ import NotFound from './pages/NotFound'; export default function App() { const { user, loading } = useAuth(); - const { setLanguage } = useLanguage(); + const { t, setLanguage } = useLanguage(); const { appName } = useBranding(); + const location = useLocation(); // Sync language from server when user loads useEffect(() => { @@ -34,14 +35,28 @@ export default function App() { } }, [user?.language, setLanguage]); - // Update document title with branding + // Unique, context-forward document title per page (SPA route changes + // don't reload the document, so we update the title ourselves) useEffect(() => { - document.title = `${appName} - BigBlueButton Frontend`; - }, [appName]); + const pageTitles = { + '/dashboard': t('nav.dashboard'), + '/calendar': t('nav.calendar'), + '/settings': t('nav.settings'), + '/admin': t('nav.admin'), + '/federation': t('nav.federation'), + '/login': t('auth.login'), + '/register': t('auth.register'), + '/forgot-password': t('auth.forgotPassword'), + }; + const match = Object.entries(pageTitles).find(([path]) => + location.pathname === path || location.pathname.startsWith(`${path}/`) + ); + document.title = match ? `${match[1]} | ${appName}` : `${appName} - BigBlueButton Frontend`; + }, [location.pathname, appName, t]); if (loading) { return ( -
+
); diff --git a/src/components/AnalyticsList.jsx b/src/components/AnalyticsList.jsx index 76e86cb..d68bac5 100644 --- a/src/components/AnalyticsList.jsx +++ b/src/components/AnalyticsList.jsx @@ -211,21 +211,21 @@ export default function AnalyticsList({ analytics, onRefresh, isOwner = true }) - - - + + - - - - + diff --git a/src/components/DateTimePicker.jsx b/src/components/DateTimePicker.jsx index cf69c9c..ed86aa2 100644 --- a/src/components/DateTimePicker.jsx +++ b/src/components/DateTimePicker.jsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useId, useRef } from 'react'; import flatpickr from 'flatpickr'; import 'flatpickr/dist/flatpickr.min.css'; import { German } from 'flatpickr/dist/l10n/de.js'; @@ -30,6 +30,7 @@ export default function DateTimePicker({ }) { const inputRef = useRef(null); const fpRef = useRef(null); + const inputId = useId(); // Always keep a current ref to onChange so flatpickr's closure never goes stale const onChangeRef = useRef(onChange); useEffect(() => { onChangeRef.current = onChange; }); @@ -85,13 +86,14 @@ export default function DateTimePicker({ return (
{label && ( -
- - - - - @@ -970,19 +991,19 @@ export default function Admin() {
{t('analytics.userName')}{t('analytics.role')} + {t('analytics.userName')}{t('analytics.role')} {t('analytics.duration')} + {t('analytics.talkTime')} + {t('analytics.messages')} + {t('analytics.raiseHand')} {t('analytics.reactions')}{t('analytics.reactions')}
+ {t('admin.roomName')} + {t('admin.roomOwner')} + {t('admin.roomShares')} + {t('admin.roomCreated')} + {t('admin.actions')}
- - - - - @@ -1052,6 +1073,8 @@ export default function Admin() { }} className="p-1.5 rounded-lg hover:bg-th-hover text-th-text-s transition-colors" disabled={u.id === user.id} + aria-label={`${t('admin.userActions')}: ${u.display_name || u.name}`} + aria-expanded={openMenu === u.id} > @@ -1112,7 +1135,7 @@ export default function Admin() { {resetPwModal && (
setResetPwModal(null)} aria-hidden="true" /> -
+

{t('admin.resetPasswordTitle')}

@@ -1146,7 +1169,7 @@ export default function Admin() { {showCreateUser && (
setShowCreateUser(false)} aria-hidden="true" /> -
+

{t('admin.createUserTitle')}

@@ -1242,12 +1265,12 @@ export default function Admin() { {/* All rooms modal */} {showAllRoomsModal && (
-
setShowAllRoomsModal(false)} /> -
+
setShowAllRoomsModal(false)} aria-hidden="true" /> +
- -

{t('admin.roomsTitle')}

+
+ {t('admin.user')} + {t('admin.role')} + {t('admin.rooms')} + {t('admin.registered')} + {t('admin.actions')}
- - - - - @@ -1321,6 +1344,7 @@ export default function Admin() { -

{monthLabel}

diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index faa644f..427fe30 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -85,7 +85,7 @@ export default function Dashboard() { if (loading) { return ( -
+
); diff --git a/src/pages/FederatedRoomDetail.jsx b/src/pages/FederatedRoomDetail.jsx index 30fc802..a45fc1c 100644 --- a/src/pages/FederatedRoomDetail.jsx +++ b/src/pages/FederatedRoomDetail.jsx @@ -67,7 +67,7 @@ export default function FederatedRoomDetail() { if (loading) { return ( -
+
); diff --git a/src/pages/FederationInbox.jsx b/src/pages/FederationInbox.jsx index 1233b66..bd62f0c 100644 --- a/src/pages/FederationInbox.jsx +++ b/src/pages/FederationInbox.jsx @@ -127,7 +127,7 @@ export default function FederationInbox() { if (loading) { return ( -
+
); diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index b7f85a4..8107021 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -106,7 +106,7 @@ export default function Login() { return (
{/* Animated background */} -
+
+ {t('admin.roomName')} + {t('admin.roomOwner')} + {t('admin.roomShares')} + {t('admin.roomCreated')} + {t('admin.actions')}