feat(admin): add the admin users page and view as
Build, Test & Push Frontend / quality-check (push) Successful in 17s
Build, Test & Push Frontend / docker (push) Successful in 28s

This commit is contained in:
lobo committed 2026-09-27 12:18:10 +02:00
1 parent 46587dfb5c
commit afe00ceae9
14 files changed
+649 -8

No files matched your search

+56 -2
View File
@@ -1,13 +1,18 @@
import {Link, NavLink, useLocation} from 'react-router-dom';
import {Link, NavLink, useLocation, useNavigate} from 'react-router-dom';
import {useTranslation} from 'react-i18next';
import {ChartColumn, Dices, Languages, LayoutDashboard, Library, LogOut, Moon, Settings, Sun} from 'lucide-react';
import {ChartColumn, Dices, Eye, Languages, LayoutDashboard, Library, LogOut, Moon, Settings, Shield, Sun} from 'lucide-react';
import {toast} from 'sonner';
import {Badge} from '@/components/ui/badge';
import {Button} from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {usePreferences} from '@/context/PreferencesContext.jsx';
@@ -15,7 +20,11 @@ import {cn} from '@/lib/utils';
import Logo from './Logo.jsx';
import PageContainer from './PageContainer.jsx';
import SteamAvatar from './SteamAvatar.jsx';
import useCurrentUser from '../hooks/useCurrentUser.js';
import useSessionExpired from '../hooks/useSessionExpired.js';
import useSteamProfile from '../hooks/useSteamProfile.js';
import {ROLES, viewAs} from '../utils/admin.js';
import {markSession} from '../utils/auth.js';
const NAV_ITEMS = [
{to: '/dashboard', labelKey: 'NAV.DASHBOARD', Icon: LayoutDashboard},
@@ -37,6 +46,30 @@ export default function AppHeader({isAuthenticated, onLogout}) {
const {isDarkMode, setDarkMode, setLanguage} = usePreferences();
const {pathname} = useLocation();
const steamProfile = useSteamProfile(isAuthenticated);
const currentUser = useCurrentUser(isAuthenticated);
const navigate = useNavigate();
const expireSession = useSessionExpired();
const isAdmin = currentUser?.role === 'PLATFORM_ADMIN';
const viewingAs = currentUser && currentUser.activeRole !== currentUser.role ? currentUser.activeRole : null;
const switchRole = async (role) => {
try {
const response = await viewAs(role === currentUser.role ? null : role);
if (response.ok) {
// New cookies with the other role; the new stamp makes the header and pages load again.
markSession();
navigate('/dashboard');
return;
}
if (response.status === 401) {
expireSession();
return;
}
} catch (err) {
console.error('View as error:', err);
}
toast.error(t('NAV.VIEW_AS.ERROR'));
};
const toggleLanguage = () => setLanguage(i18n.language === 'de' ? 'en' : 'de');
const toggleTheme = () => setDarkMode(!isDarkMode);
@@ -64,6 +97,9 @@ export default function AppHeader({isAuthenticated, onLogout}) {
)}
<div className="ml-auto flex items-center gap-1">
{isAuthenticated && viewingAs && (
<Badge variant="secondary" className="mr-1">{t('NAV.VIEW_AS.BADGE', {role: t(`ROLES.${viewingAs}`)})}</Badge>
)}
{isAuthenticated ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -75,6 +111,24 @@ export default function AppHeader({isAuthenticated, onLogout}) {
<DropdownMenuItem asChild>
<Link to="/settings"><Settings/>{t('NAV.SETTINGS')}</Link>
</DropdownMenuItem>
{isAdmin && !viewingAs && (
<DropdownMenuItem asChild>
<Link to="/admin/users"><Shield/>{t('NAV.ADMIN')}</Link>
</DropdownMenuItem>
)}
{isAdmin && (
<DropdownMenuSub>
<DropdownMenuSubTrigger><Eye/>{t('NAV.VIEW_AS.MENU')}</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{ROLES.map((role) => (
<DropdownMenuItem key={role} disabled={role === currentUser.activeRole}
onSelect={() => switchRole(role)}>
{t(`ROLES.${role}`)}
</DropdownMenuItem>
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
)}
<DropdownMenuItem onSelect={toggleLanguage}>
<Languages/>{t('NAV.LANGUAGE')}: {i18n.language.toUpperCase()}
</DropdownMenuItem>