diff --git a/frontend/src/components/layout/Sidebar.test.tsx b/frontend/src/components/layout/Sidebar.test.tsx index ee1bbbf..ff7dee3 100644 --- a/frontend/src/components/layout/Sidebar.test.tsx +++ b/frontend/src/components/layout/Sidebar.test.tsx @@ -21,34 +21,34 @@ function mockAuthenticatedAs(role: 'Owner' | 'Administrator' | 'User') { } describe('Sidebar role filtering (BR-U3-01 – BR-U3-06)', () => { - it('Owner sees all nav items', async () => { + it('Owner sees Dashboard, Users, Settings, CMS', async () => { mockAuthenticatedAs('Owner'); renderApp('/dashboard'); expect(await screen.findByTestId('nav-dashboard')).toBeInTheDocument(); expect(screen.getByTestId('nav-users')).toBeInTheDocument(); expect(screen.getByTestId('nav-settings')).toBeInTheDocument(); expect(screen.getByTestId('nav-cms')).toBeInTheDocument(); - expect(screen.getByTestId('nav-profile')).toBeInTheDocument(); + expect(screen.queryByTestId('nav-profile')).not.toBeInTheDocument(); }); - it('Administrator sees Dashboard, Users, Profile — not Settings or CMS', async () => { + it('Administrator sees Dashboard and Users — not Settings or CMS', async () => { mockAuthenticatedAs('Administrator'); renderApp('/dashboard'); expect(await screen.findByTestId('nav-dashboard')).toBeInTheDocument(); expect(screen.getByTestId('nav-users')).toBeInTheDocument(); - expect(screen.getByTestId('nav-profile')).toBeInTheDocument(); expect(screen.queryByTestId('nav-settings')).not.toBeInTheDocument(); expect(screen.queryByTestId('nav-cms')).not.toBeInTheDocument(); + expect(screen.queryByTestId('nav-profile')).not.toBeInTheDocument(); }); - it('User sees Dashboard and Profile only', async () => { + it('User sees Dashboard only', async () => { mockAuthenticatedAs('User'); renderApp('/dashboard'); expect(await screen.findByTestId('nav-dashboard')).toBeInTheDocument(); - expect(screen.getByTestId('nav-profile')).toBeInTheDocument(); expect(screen.queryByTestId('nav-users')).not.toBeInTheDocument(); expect(screen.queryByTestId('nav-settings')).not.toBeInTheDocument(); expect(screen.queryByTestId('nav-cms')).not.toBeInTheDocument(); + expect(screen.queryByTestId('nav-profile')).not.toBeInTheDocument(); }); }); diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index f4af794..4481557 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -1,6 +1,6 @@ import { Link } from '@tanstack/react-router'; import { useTranslation } from 'react-i18next'; -import { LayoutDashboard, Users, FileText, Settings, User, X } from 'lucide-react'; +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'; @@ -23,7 +23,6 @@ const NAV_ITEMS: NavItem[] = [ { 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'] }, - { to: '/profile', labelKey: 'nav.profile', icon: User, testId: 'nav-profile' }, ]; interface SidebarProps { diff --git a/frontend/src/components/layout/UserMenu.tsx b/frontend/src/components/layout/UserMenu.tsx index 289f3c5..969bd8b 100644 --- a/frontend/src/components/layout/UserMenu.tsx +++ b/frontend/src/components/layout/UserMenu.tsx @@ -1,6 +1,7 @@ import { useNavigate } from '@tanstack/react-router'; import { useTranslation } from 'react-i18next'; import { LogOut, User as UserIcon } from 'lucide-react'; +import { Link } from '@tanstack/react-router'; import { Button } from '@/components/ui/button'; import { DropdownMenu, @@ -46,6 +47,13 @@ export function UserMenu() { + + + + {t('nav.profile')} + + + { void handleLogout(); diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx new file mode 100644 index 0000000..c0de36f --- /dev/null +++ b/frontend/src/pages/ProfilePage.tsx @@ -0,0 +1,13 @@ +import { useTranslation } from 'react-i18next'; + +export function ProfilePage() { + const { t } = useTranslation(); + return ( +
+

+ {t('nav.profile')} +

+

Coming soon.

+
+ ); +} diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx new file mode 100644 index 0000000..ecb83c9 --- /dev/null +++ b/frontend/src/pages/SettingsPage.tsx @@ -0,0 +1,13 @@ +import { useTranslation } from 'react-i18next'; + +export function SettingsPage() { + const { t } = useTranslation(); + return ( +
+

+ {t('nav.settings')} +

+

Coming soon.

+
+ ); +} diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 3dbf2e6..86ee94f 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -189,12 +189,28 @@ const cmsRoute = createRoute({ ), }); +const settingsRoute = createRoute({ + getParentRoute: () => authenticatedRoute, + path: '/settings', + component: () => ( + + {lazyPage(() => import('@/pages/SettingsPage'), 'SettingsPage')()} + + ), +}); + +const profileRoute = createRoute({ + getParentRoute: () => authenticatedRoute, + path: '/profile', + component: lazyPage(() => import('@/pages/ProfilePage'), 'ProfilePage'), +}); + export const routeTree = rootRoute.addChildren([ indexRoute, loginRoute, setupRoute, inviteCompleteRoute, - authenticatedRoute.addChildren([dashboardRoute, usersRoute, cmsRoute]), + authenticatedRoute.addChildren([dashboardRoute, usersRoute, cmsRoute, settingsRoute, profileRoute]), ]); export const router = createRouter({