fix(unit-3): Add settings/profile routes, move profile to UserMenu
- Add /settings (Owner only) and /profile routes under authenticatedRoute so sidebar stays visible and layout is preserved - Create SettingsPage and ProfilePage placeholder components (coming soon) - Remove Profile nav item from Sidebar — profile now accessible via UserMenu - Add Profile link to UserMenu dropdown above logout - Update Sidebar tests to reflect profile-free nav Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,34 +21,34 @@ function mockAuthenticatedAs(role: 'Owner' | 'Administrator' | 'User') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('Sidebar role filtering (BR-U3-01 – BR-U3-06)', () => {
|
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');
|
mockAuthenticatedAs('Owner');
|
||||||
renderApp('/dashboard');
|
renderApp('/dashboard');
|
||||||
expect(await screen.findByTestId('nav-dashboard')).toBeInTheDocument();
|
expect(await screen.findByTestId('nav-dashboard')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('nav-users')).toBeInTheDocument();
|
expect(screen.getByTestId('nav-users')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('nav-settings')).toBeInTheDocument();
|
expect(screen.getByTestId('nav-settings')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('nav-cms')).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');
|
mockAuthenticatedAs('Administrator');
|
||||||
renderApp('/dashboard');
|
renderApp('/dashboard');
|
||||||
expect(await screen.findByTestId('nav-dashboard')).toBeInTheDocument();
|
expect(await screen.findByTestId('nav-dashboard')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('nav-users')).toBeInTheDocument();
|
expect(screen.getByTestId('nav-users')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('nav-profile')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByTestId('nav-settings')).not.toBeInTheDocument();
|
expect(screen.queryByTestId('nav-settings')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId('nav-cms')).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');
|
mockAuthenticatedAs('User');
|
||||||
renderApp('/dashboard');
|
renderApp('/dashboard');
|
||||||
expect(await screen.findByTestId('nav-dashboard')).toBeInTheDocument();
|
expect(await screen.findByTestId('nav-dashboard')).toBeInTheDocument();
|
||||||
expect(screen.getByTestId('nav-profile')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByTestId('nav-users')).not.toBeInTheDocument();
|
expect(screen.queryByTestId('nav-users')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId('nav-settings')).not.toBeInTheDocument();
|
expect(screen.queryByTestId('nav-settings')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId('nav-cms')).not.toBeInTheDocument();
|
expect(screen.queryByTestId('nav-cms')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('nav-profile')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Link } from '@tanstack/react-router';
|
import { Link } from '@tanstack/react-router';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 type { LucideIcon } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useAuth } from '@/contexts/auth-context';
|
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: '/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: '/settings', labelKey: 'nav.settings', icon: Settings, testId: 'nav-settings', roles: ['Owner'] },
|
||||||
{ to: '/cms', labelKey: 'nav.cms', icon: FileText, testId: 'nav-cms', 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 {
|
interface SidebarProps {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useNavigate } from '@tanstack/react-router';
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { LogOut, User as UserIcon } from 'lucide-react';
|
import { LogOut, User as UserIcon } from 'lucide-react';
|
||||||
|
import { Link } from '@tanstack/react-router';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -46,6 +47,13 @@ export function UserMenu() {
|
|||||||
</div>
|
</div>
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<Link to="/profile" data-testid="user-menu-profile">
|
||||||
|
<UserIcon />
|
||||||
|
{t('nav.profile')}
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
void handleLogout();
|
void handleLogout();
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export function ProfilePage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h1 className="text-2xl font-semibold" data-testid="profile-title">
|
||||||
|
{t('nav.profile')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground">Coming soon.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export function SettingsPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h1 className="text-2xl font-semibold" data-testid="settings-title">
|
||||||
|
{t('nav.settings')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground">Coming soon.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+17
-1
@@ -189,12 +189,28 @@ const cmsRoute = createRoute({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const settingsRoute = createRoute({
|
||||||
|
getParentRoute: () => authenticatedRoute,
|
||||||
|
path: '/settings',
|
||||||
|
component: () => (
|
||||||
|
<RoleGuard allowedRoles={['Owner']}>
|
||||||
|
{lazyPage(() => import('@/pages/SettingsPage'), 'SettingsPage')()}
|
||||||
|
</RoleGuard>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const profileRoute = createRoute({
|
||||||
|
getParentRoute: () => authenticatedRoute,
|
||||||
|
path: '/profile',
|
||||||
|
component: lazyPage(() => import('@/pages/ProfilePage'), 'ProfilePage'),
|
||||||
|
});
|
||||||
|
|
||||||
export const routeTree = rootRoute.addChildren([
|
export const routeTree = rootRoute.addChildren([
|
||||||
indexRoute,
|
indexRoute,
|
||||||
loginRoute,
|
loginRoute,
|
||||||
setupRoute,
|
setupRoute,
|
||||||
inviteCompleteRoute,
|
inviteCompleteRoute,
|
||||||
authenticatedRoute.addChildren([dashboardRoute, usersRoute, cmsRoute]),
|
authenticatedRoute.addChildren([dashboardRoute, usersRoute, cmsRoute, settingsRoute, profileRoute]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
|
|||||||
Reference in New Issue
Block a user