diff --git a/src/__tests__/auth/user-menu.test.tsx b/src/__tests__/auth/user-menu.test.tsx index 78a12c1..050587f 100644 --- a/src/__tests__/auth/user-menu.test.tsx +++ b/src/__tests__/auth/user-menu.test.tsx @@ -92,7 +92,7 @@ describe('UserMenu', () => { }) }) - it('has settings link in dropdown', async () => { + it('has account settings link in dropdown', async () => { mockUseAuth.mockReturnValue(createMockAuthContext()) const user = userEvent.setup() @@ -100,10 +100,37 @@ describe('UserMenu', () => { await user.click(screen.getByRole('button', { name: /user menu/i })) await waitFor(() => { - expect(screen.getByRole('menuitem', { name: /settings/i })).toBeInTheDocument() + expect(screen.getByRole('menuitem', { name: /account settings/i })).toBeInTheDocument() }) }) + it('shows admin panel link for admin users', async () => { + mockUseAuth.mockReturnValue(createMockAuthContext({ user: { ...mockUser, role: 'admin' } })) + const user = userEvent.setup() + + render() + await user.click(screen.getByRole('button', { name: /user menu/i })) + + await waitFor(() => { + const adminLink = screen.getByRole('menuitem', { name: /admin panel/i }) + expect(adminLink).toBeInTheDocument() + expect(adminLink).toHaveAttribute('href', '/admin') + }) + }) + + it('hides admin panel link for non-admin users', async () => { + mockUseAuth.mockReturnValue(createMockAuthContext()) + const user = userEvent.setup() + + render() + await user.click(screen.getByRole('button', { name: /user menu/i })) + + await waitFor(() => { + expect(screen.getByRole('menuitem', { name: /account settings/i })).toBeInTheDocument() + }) + expect(screen.queryByRole('menuitem', { name: /admin panel/i })).not.toBeInTheDocument() + }) + it('has logout option in dropdown', async () => { mockUseAuth.mockReturnValue(createMockAuthContext()) const user = userEvent.setup() diff --git a/src/components/auth/user-menu.tsx b/src/components/auth/user-menu.tsx index 91ee081..5123ae3 100644 --- a/src/components/auth/user-menu.tsx +++ b/src/components/auth/user-menu.tsx @@ -8,7 +8,7 @@ import Link from 'next/link' import Image from 'next/image' -import { User, SignOut, GearSix } from '@phosphor-icons/react' +import { User, SignOut, GearSix, ShieldCheck } from '@phosphor-icons/react' import { useAuth } from '@/hooks/use-auth' import { DropdownMenu, @@ -80,10 +80,19 @@ export function UserMenu() { + {user.role === 'admin' && ( + + + + )} + { it('executes action when authenticated', () => { mockedUseAuth.mockReturnValue({ - user: { did: 'did:plc:test', handle: 'test.bsky.social', displayName: null, avatarUrl: null }, + user: { + did: 'did:plc:test', + handle: 'test.bsky.social', + displayName: null, + avatarUrl: null, + role: 'user' as const, + }, isAuthenticated: true, isLoading: false, crossPostScopesGranted: false, diff --git a/src/lib/api/types.ts b/src/lib/api/types.ts index 4d8d67c..b6c12c2 100644 --- a/src/lib/api/types.ts +++ b/src/lib/api/types.ts @@ -214,6 +214,8 @@ export interface CommunityStats { // --- Auth --- +export type UserRole = 'user' | 'moderator' | 'admin' + export interface AuthSession { accessToken: string expiresAt: string @@ -221,6 +223,7 @@ export interface AuthSession { handle: string displayName: string | null avatarUrl: string | null + role: UserRole crossPostScopesGranted?: boolean } @@ -229,6 +232,7 @@ export interface AuthUser { handle: string displayName: string | null avatarUrl: string | null + role: UserRole } // --- Notifications --- diff --git a/src/mocks/data.ts b/src/mocks/data.ts index 5087e5b..4323d62 100644 --- a/src/mocks/data.ts +++ b/src/mocks/data.ts @@ -77,6 +77,7 @@ export const mockAuthSession: AuthSession = { handle: 'alice.bsky.social', displayName: 'Alice', avatarUrl: 'https://cdn.bsky.social/avatar/alice.jpg', + role: 'user', } export const mockAuthUser: AuthUser = { @@ -84,6 +85,7 @@ export const mockAuthUser: AuthUser = { handle: 'alice.bsky.social', displayName: 'Alice', avatarUrl: 'https://cdn.bsky.social/avatar/alice.jpg', + role: 'user', } // --- Categories --- diff --git a/src/test/mock-auth.tsx b/src/test/mock-auth.tsx index c0dfdc7..5a96526 100644 --- a/src/test/mock-auth.tsx +++ b/src/test/mock-auth.tsx @@ -13,6 +13,7 @@ export const mockUser: AuthUser = { handle: 'alice.bsky.social', displayName: 'Alice', avatarUrl: 'https://cdn.bsky.social/avatar/alice.jpg', + role: 'user', } export function createMockAuthContext(overrides: Partial = {}): AuthContextValue {