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() {
- Settings
+ Account settings
+ {user.role === 'admin' && (
+
+
+
+ Admin Panel
+
+
+ )}
+
{
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 {