INSIGHT-MVP/packages/frontend/src/shell/AppLayout.tsx
Thomas Reitz 6fa86714db feat: add profile picture upload, sidebar hint, and fix 2FA bugs
- 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>
2026-03-09 06:51:27 +01:00

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 &rarr;</div>
</div>
<button className={styles.logoutBtn} onClick={handleLogout}>
Abmelden
</button>
</div>
</aside>
{/* Main Content */}
<main className={styles.main}>
<Outlet />
</main>
</div>
);
}