Adds profile and settings pages

This commit is contained in:
2026-06-22 23:59:04 +02:00
parent 6976eb4337
commit 2544e20b3c
49 changed files with 2741 additions and 34 deletions
+40 -1
View File
@@ -4,6 +4,7 @@ import { LayoutDashboard, Users, FileText, Settings, X } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useAuth } from '@/contexts/auth-context';
import { useAvailabilityStatus } from '@/api/useAvailability';
import { LanguageSwitcher } from '@/i18n/LanguageSwitcher';
import { ThemeToggle } from './ThemeToggle';
import { UserMenu } from './UserMenu';
@@ -18,13 +19,18 @@ interface NavItem {
roles?: Role[];
}
const ALLOWED_WHEN_UNAVAILABLE = ['/dashboard', '/settings'];
const NAV_ITEMS: NavItem[] = [
{ to: '/dashboard', labelKey: 'nav.dashboard', icon: LayoutDashboard, testId: 'nav-dashboard' },
{ to: '/users', labelKey: 'nav.users', icon: Users, testId: 'nav-users', roles: ['Owner', 'Administrator'] },
{ to: '/settings', labelKey: 'nav.settings', icon: Settings, testId: 'nav-settings', roles: ['Owner'] },
{ to: '/cms', labelKey: 'nav.cms', icon: FileText, testId: 'nav-cms', roles: ['Owner'] },
];
const SETTINGS_ITEM: NavItem = {
to: '/settings', labelKey: 'nav.settings', icon: Settings, testId: 'nav-settings', roles: ['Owner'],
};
interface SidebarProps {
onClose?: () => void;
}
@@ -32,11 +38,14 @@ interface SidebarProps {
export function Sidebar({ onClose }: SidebarProps = {}) {
const { t } = useTranslation();
const { user } = useAuth();
const { data: availability } = useAvailabilityStatus();
const role = user?.role as Role | undefined;
const systemUnavailable = availability?.status === 'NotAvailable';
const visibleItems = NAV_ITEMS.filter(
(item) => !item.roles || (role && item.roles.includes(role))
);
const showSettings = !SETTINGS_ITEM.roles || (role && SETTINGS_ITEM.roles.includes(role));
return (
<aside
@@ -64,6 +73,19 @@ export function Sidebar({ onClose }: SidebarProps = {}) {
<nav className="flex-1 space-y-1 p-3">
{visibleItems.map((item) => {
const Icon = item.icon;
const blocked = systemUnavailable && !ALLOWED_WHEN_UNAVAILABLE.some(p => item.to.startsWith(p));
if (blocked) {
return (
<span
key={item.to}
data-testid={item.testId}
className="flex cursor-not-allowed items-center gap-3 rounded-md px-3 py-2 text-sm font-medium opacity-40 select-none"
>
<Icon className="size-4" />
{t(item.labelKey)}
</span>
);
}
return (
<Link
key={item.to}
@@ -82,6 +104,23 @@ export function Sidebar({ onClose }: SidebarProps = {}) {
})}
</nav>
{showSettings && (
<div className="border-t border-border p-3">
<Link
to={SETTINGS_ITEM.to}
data-testid={SETTINGS_ITEM.testId}
onClick={onClose}
className="flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
activeProps={{
className: cn('bg-accent text-accent-foreground'),
}}
>
<Settings className="size-4" />
{t(SETTINGS_ITEM.labelKey)}
</Link>
</div>
)}
<div className="flex items-center gap-1 border-t border-border p-3">
<div className="flex-1 min-w-0">
<UserMenu />