fix: move community link to user menu (#442)

## Summary
- Move Community link from sidebar footer into user dropdown above
Feedback.
- Hide empty sidebar footer nav group.
- Add tests for dropdown order and footer removal.

## Tests
- `npm test -- --run resources/js/components/user-menu-content.test.tsx
resources/js/providers/menu-item-provider.test.ts`

Note: `npm run types` currently fails on unrelated existing TypeScript
errors.
This commit is contained in:
Víctor Falcón 2026-05-27 17:39:26 +02:00 committed by GitHub
parent 10da06ed84
commit 4f46ae3e2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 122 additions and 10 deletions

View File

@ -78,7 +78,9 @@ export function AppSidebar() {
</SidebarContent>
<SidebarFooter>
<NavFooter items={footerNavItems} className="mt-auto" />
{footerNavItems.length > 0 && (
<NavFooter items={footerNavItems} className="mt-auto" />
)}
<NavUser />
</SidebarFooter>
</Sidebar>

View File

@ -0,0 +1,95 @@
import { type User } from '@/types';
import { render, screen } from '@testing-library/react';
import { type ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { UserMenuContent } from './user-menu-content';
vi.mock('@/components/ui/dropdown-menu', () => ({
DropdownMenuGroup: ({ children }: { children: ReactNode }) => (
<div>{children}</div>
),
DropdownMenuItem: ({ children }: { children: ReactNode }) => (
<div>{children}</div>
),
DropdownMenuLabel: ({ children }: { children: ReactNode }) => (
<div>{children}</div>
),
DropdownMenuSeparator: () => <hr />,
}));
vi.mock('@/components/user-info', () => ({
UserInfo: () => <div>User</div>,
}));
vi.mock('@/contexts/privacy-mode-context', () => ({
usePrivacyMode: () => ({
isPrivacyModeEnabled: false,
togglePrivacyMode: vi.fn(),
}),
}));
vi.mock('@/hooks/use-mobile-navigation', () => ({
useMobileNavigation: () => vi.fn(),
}));
vi.mock('@/lib/key-storage', () => ({
clearKey: vi.fn(),
}));
vi.mock('@inertiajs/react', () => ({
Link: ({
children,
href,
...props
}: {
children: ReactNode;
href: string;
as?: string;
prefetch?: boolean;
}) => {
delete props.as;
delete props.prefetch;
return (
<a href={href} {...props}>
{children}
</a>
);
},
router: {
flushAll: vi.fn(),
},
usePage: () => ({
props: {
version: 'test-version',
},
}),
}));
describe('UserMenuContent', () => {
it('shows community above feedback in the user dropdown', () => {
const user: User = {
id: '0194d20b-2b25-7000-8000-000000000001',
name: 'Test User',
email: 'test@example.com',
currency_code: 'USD',
locale: 'en',
timezone: 'UTC',
email_verified_at: null,
created_at: '2026-01-01T00:00:00.000000Z',
updated_at: '2026-01-01T00:00:00.000000Z',
};
render(<UserMenuContent user={user} />);
const community = screen.getByRole('link', { name: /community/i });
const feedback = screen.getByRole('link', { name: /feedback/i });
expect(community.getAttribute('href')).toBe(
'https://discord.gg/2WZmDW9QZ8',
);
expect(community.compareDocumentPosition(feedback)).toBe(
Node.DOCUMENT_POSITION_FOLLOWING,
);
});
});

View File

@ -1,3 +1,4 @@
import DiscordIcon from '@/components/icons/DiscordIcon';
import {
DropdownMenuGroup,
DropdownMenuItem,
@ -92,6 +93,18 @@ export function UserMenuContent({ user }: UserMenuContentProps) {
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<a
className="block w-full cursor-pointer"
href="https://discord.gg/2WZmDW9QZ8"
target="_blank"
rel="noopener noreferrer"
onClick={cleanup}
>
<DiscordIcon className="mr-2 size-4" />
{__('Community')}
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a
className="block w-full cursor-pointer"

View File

@ -0,0 +1,10 @@
import { describe, expect, it } from 'vitest';
import { footerNavItems } from './menu-item-provider';
describe('menu item provider', () => {
it('does not include community in sidebar footer nav items', () => {
expect(footerNavItems).not.toContainEqual(
expect.objectContaining({ title: 'Community' }),
);
});
});

View File

@ -1,7 +1,6 @@
import { index as accountsIndex } from '@/actions/App/Http/Controllers/AccountController';
import { index as budgetsIndex } from '@/actions/App/Http/Controllers/BudgetController';
import { index as transactionsIndex } from '@/actions/App/Http/Controllers/TransactionController';
import DiscordIcon from '@/components/icons/DiscordIcon';
import { cashflow, dashboard } from '@/routes';
import { Features, NavItem } from '@/types';
import {
@ -81,11 +80,4 @@ export function getMainNavItems(features: Features, locale: string): NavItem[] {
return items;
}
export const footerNavItems: NavItem[] = [
{
type: 'nav-item',
title: 'Community',
href: 'https://discord.gg/2WZmDW9QZ8',
icon: <DiscordIcon className="size-5" />,
},
];
export const footerNavItems: NavItem[] = [];