mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 05:56:39 +02:00
- Bug fix: include twoFactorEnabled in login response so ProfilePage shows correct 2FA status after login (not always "Aktivieren") - Bug fix: restructure 2FA enable/disable handlers to separate API call from state updates, preventing false error messages on success - New: avatar field in User model (Base64 data-URL in PostgreSQL TEXT) - New: UserAvatar component with initials fallback - New: client-side image resize to 200x200px before upload - New: avatar upload/remove on ProfilePage with preview - New: avatar display + "Zum Profil" hint in sidebar - Increase JSON body size limit to 1mb for avatar uploads Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
96 lines
2.8 KiB
TypeScript
96 lines
2.8 KiB
TypeScript
import { Outlet, NavLink, useNavigate } from 'react-router-dom';
|
|
import { useAuth } from '../auth/AuthContext';
|
|
import { UserAvatar } from '../components/UserAvatar';
|
|
import styles from './AppLayout.module.css';
|
|
|
|
export function AppLayout() {
|
|
const { user, logout } = useAuth();
|
|
const navigate = useNavigate();
|
|
|
|
const handleLogout = async () => {
|
|
await logout();
|
|
navigate('/login');
|
|
};
|
|
|
|
return (
|
|
<div className={styles.layout}>
|
|
{/* Sidebar */}
|
|
<aside className={styles.sidebar}>
|
|
<div className={styles.brand}>
|
|
<h2>INSIGHT</h2>
|
|
</div>
|
|
|
|
<nav className={styles.nav}>
|
|
<NavLink
|
|
to="/"
|
|
end
|
|
className={({ isActive }) =>
|
|
`${styles.navLink} ${isActive ? styles.active : ''}`
|
|
}
|
|
>
|
|
Dashboard
|
|
</NavLink>
|
|
|
|
{/* Admin-Bereich (nur fuer PLATFORM_ADMIN) */}
|
|
{user?.role === 'PLATFORM_ADMIN' && (
|
|
<>
|
|
<div className={styles.navSection}>Administration</div>
|
|
<NavLink
|
|
to="/admin/users"
|
|
className={({ isActive }) =>
|
|
`${styles.navLink} ${isActive ? styles.active : ''}`
|
|
}
|
|
>
|
|
Benutzer
|
|
</NavLink>
|
|
<NavLink
|
|
to="/admin/tenants"
|
|
className={({ isActive }) =>
|
|
`${styles.navLink} ${isActive ? styles.active : ''}`
|
|
}
|
|
>
|
|
Mandanten
|
|
</NavLink>
|
|
</>
|
|
)}
|
|
</nav>
|
|
|
|
<div className={styles.userInfo}>
|
|
<div
|
|
className={styles.userProfile}
|
|
onClick={() => navigate('/profile')}
|
|
role="button"
|
|
tabIndex={0}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') navigate('/profile');
|
|
}}
|
|
>
|
|
<div className={styles.userProfileInner}>
|
|
<UserAvatar
|
|
firstName={user?.firstName ?? ''}
|
|
lastName={user?.lastName ?? ''}
|
|
avatar={user?.avatar}
|
|
size={36}
|
|
/>
|
|
<div className={styles.userDetails}>
|
|
<div className={styles.userName}>
|
|
{user?.firstName} {user?.lastName}
|
|
</div>
|
|
<div className={styles.userEmail}>{user?.email}</div>
|
|
</div>
|
|
</div>
|
|
<div className={styles.profileHint}>Zum Profil →</div>
|
|
</div>
|
|
<button className={styles.logoutBtn} onClick={handleLogout}>
|
|
Abmelden
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Main Content */}
|
|
<main className={styles.main}>
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|