diff --git a/src/app/admin/plugins/page.test.tsx b/src/app/admin/plugins/page.test.tsx index 4a06aed..ad6c405 100644 --- a/src/app/admin/plugins/page.test.tsx +++ b/src/app/admin/plugins/page.test.tsx @@ -1,15 +1,13 @@ /** - * Tests for admin plugins page. + * Tests for admin plugins page (P3 placeholder). * @see specs/prd-web.md Section M13 */ -import { describe, it, expect } from 'vitest' -import { render, screen, waitFor, within } from '@testing-library/react' -import userEvent from '@testing-library/user-event' +import { describe, it, expect, vi } from 'vitest' +import { render, screen } from '@testing-library/react' import { axe } from 'vitest-axe' import AdminPluginsPage from './page' -// Mock next/navigation vi.mock('next/navigation', () => ({ usePathname: () => '/admin/plugins', })) @@ -33,110 +31,20 @@ vi.mock('@/hooks/use-auth', () => { return { useAuth: () => mockAuth } }) -vi.mock('@/hooks/use-toast', () => ({ - useToast: () => ({ toast: vi.fn(), dismiss: vi.fn() }), -})) - describe('AdminPluginsPage', () => { - it('renders page heading', async () => { - render() - await waitFor(() => { - expect(screen.getByRole('heading', { name: /plugins/i, level: 1 })).toBeInTheDocument() - }) - }) - - it('renders plugin list from API', async () => { - render() - await waitFor(() => { - expect(screen.getByText('Full-Text Search')).toBeInTheDocument() - }) - expect(screen.getByText('Markdown Editor')).toBeInTheDocument() - expect(screen.getByText('Code Highlighting')).toBeInTheDocument() - expect(screen.getByText('Community Analytics')).toBeInTheDocument() - expect(screen.getByText('Webhook Notifications')).toBeInTheDocument() - }) - - it('shows source badges (Core, Official, Community, Experimental)', async () => { - render() - await waitFor(() => { - expect(screen.getAllByText('Core').length).toBeGreaterThanOrEqual(2) - }) - expect(screen.getByText('Official')).toBeInTheDocument() - expect(screen.getByText('Community')).toBeInTheDocument() - expect(screen.getByText('Experimental')).toBeInTheDocument() - }) - - it('shows version numbers', async () => { - render() - await waitFor(() => { - expect(screen.getByText('v1.0.0')).toBeInTheDocument() - }) - expect(screen.getByText('v1.2.0')).toBeInTheDocument() - expect(screen.getByText('v0.2.0-beta')).toBeInTheDocument() - }) - - it('shows enabled/disabled state for plugins', async () => { - render() - await waitFor(() => { - expect(screen.getByText('Full-Text Search')).toBeInTheDocument() - }) - // Find toggle buttons - enabled plugins should have checked toggles - const toggles = screen.getAllByRole('switch') - expect(toggles.length).toBeGreaterThanOrEqual(5) - }) - - it('shows dependency warning when attempting to disable a plugin with dependents', async () => { - const user = userEvent.setup() - render() - await waitFor(() => { - expect(screen.getByText('Markdown Editor')).toBeInTheDocument() - }) - - // Markdown Editor has dependents: ['barazo-plugin-code-highlight'] - // Find the Markdown Editor card and its toggle - const markdownCard = screen.getByText('Markdown Editor').closest('article') - expect(markdownCard).toBeTruthy() - const toggle = within(markdownCard!).getByRole('switch') - await user.click(toggle) - - // Should show dependency warning dialog - await waitFor(() => { - expect(screen.getByText('Dependency warning')).toBeInTheDocument() - }) - expect(screen.getByText('Disable anyway')).toBeInTheDocument() - }) - - it('opens settings modal when settings button is clicked', async () => { - const user = userEvent.setup() + it('renders page heading', () => { render() - await waitFor(() => { - expect(screen.getByText('Full-Text Search')).toBeInTheDocument() - }) - - // Click settings button on Full-Text Search plugin - const searchCard = screen.getByText('Full-Text Search').closest('article') - expect(searchCard).toBeTruthy() - const settingsBtn = within(searchCard!).getByRole('button', { name: /settings/i }) - await user.click(settingsBtn) - - // Should show settings modal with schema fields - await waitFor(() => { - expect(screen.getByText('Enable semantic search')).toBeInTheDocument() - }) + expect(screen.getByRole('heading', { name: /plugins/i, level: 1 })).toBeInTheDocument() }) - it('shows plugin descriptions', async () => { + it('shows coming in P3 message', () => { render() - await waitFor(() => { - expect(screen.getByText(/full-text search for topics and replies/i)).toBeInTheDocument() - }) + expect(screen.getByRole('heading', { name: /coming in p3/i })).toBeInTheDocument() + expect(screen.getByText(/planned for the p3\.2 milestone/i)).toBeInTheDocument() }) it('passes axe accessibility check', async () => { const { container } = render() - await waitFor(() => { - expect(screen.getByText('Full-Text Search')).toBeInTheDocument() - }) const results = await axe(container) expect(results).toHaveNoViolations() }) diff --git a/src/app/admin/plugins/page.tsx b/src/app/admin/plugins/page.tsx index 83283f0..63a7e21 100644 --- a/src/app/admin/plugins/page.tsx +++ b/src/app/admin/plugins/page.tsx @@ -2,90 +2,27 @@ * Admin plugin management page. * URL: /admin/plugins * Lists installed plugins with enable/disable, settings, and uninstall controls. - * Phase 1: manage installed plugins. Phase 2: marketplace search/install. + * Backend endpoints not yet implemented (planned for P3.2). * @see specs/prd-web.md Section M13 */ -'use client' - +import { PuzzlePiece } from '@phosphor-icons/react/dist/ssr' import { AdminLayout } from '@/components/admin/admin-layout' -import { ErrorAlert } from '@/components/error-alert' -import { PluginCard } from '@/components/admin/plugins/plugin-card' -import { PluginSettingsModal } from '@/components/admin/plugins/plugin-settings-modal' -import { DependencyWarningDialog } from '@/components/admin/plugins/dependency-warning-dialog' -import { usePluginManagement } from '@/hooks/admin/use-plugin-management' export default function AdminPluginsPage() { - const { - plugins, - loading, - settingsPlugin, - setSettingsPlugin, - dependencyWarning, - setDependencyWarning, - loadError, - actionError, - setActionError, - fetchPlugins, - handleToggle, - confirmDisable, - handleSaveSettings, - handleUninstall, - } = usePluginManagement() - return (
-
-

Plugins

-

- {plugins.filter((p) => p.enabled).length} of {plugins.length} enabled +

Plugins

+ +
+ +

Coming in P3

+

+ Plugin management (install, enable/disable, configure) is planned for the P3.2 + milestone. The plugin API endpoints are not yet available.

- - {loadError && ( - void fetchPlugins()} /> - )} - - {actionError && setActionError(null)} />} - - {loading &&

Loading plugins...

} - - {!loading && plugins.length === 0 && ( -

No plugins installed.

- )} - - {!loading && plugins.length > 0 && ( -
- {plugins.map((plugin) => ( - void handleToggle(p)} - onUninstall={(p) => void handleUninstall(p)} - /> - ))} -
- )} - - {dependencyWarning && ( - void confirmDisable()} - onCancel={() => setDependencyWarning(null)} - /> - )} - - {settingsPlugin && ( - setSettingsPlugin(null)} - onSave={(settings) => void handleSaveSettings(settings)} - /> - )}
) diff --git a/src/app/admin/users/page.test.tsx b/src/app/admin/users/page.test.tsx index 0bb4060..6904682 100644 --- a/src/app/admin/users/page.test.tsx +++ b/src/app/admin/users/page.test.tsx @@ -1,9 +1,9 @@ /** - * Tests for admin user management page. + * Tests for admin user management page (P3 placeholder). */ import { describe, it, expect, vi } from 'vitest' -import { render, screen, waitFor } from '@testing-library/react' +import { render, screen } from '@testing-library/react' import { axe } from 'vitest-axe' import AdminUsersPage from './page' @@ -50,61 +50,20 @@ vi.mock('@/hooks/use-auth', () => { return { useAuth: () => mockAuth } }) -vi.mock('@/hooks/use-toast', () => ({ - useToast: () => ({ toast: vi.fn(), dismiss: vi.fn() }), -})) - describe('AdminUsersPage', () => { it('renders user management heading', () => { render() expect(screen.getByRole('heading', { name: /user management/i })).toBeInTheDocument() }) - it('renders user list from API', async () => { - render() - await waitFor(() => { - expect(screen.getByText('Jay Admin')).toBeInTheDocument() - }) - expect(screen.getByText('Alex Moderator')).toBeInTheDocument() - expect(screen.getByText('Sam Member')).toBeInTheDocument() - }) - - it('shows user roles', async () => { - render() - await waitFor(() => { - expect(screen.getByText('admin')).toBeInTheDocument() - }) - expect(screen.getByText('moderator')).toBeInTheDocument() - }) - - it('shows banned status', async () => { - render() - await waitFor(() => { - expect(screen.getByText('Morgan Banned')).toBeInTheDocument() - }) - // Morgan should show as banned - expect(screen.getByText('Banned')).toBeInTheDocument() - }) - - it('shows cross-community ban warning', async () => { - render() - await waitFor(() => { - expect(screen.getByText(/banned from 2 other communities/i)).toBeInTheDocument() - }) - }) - - it('shows ban/unban buttons', async () => { + it('shows coming in P3 message', () => { render() - await waitFor(() => { - expect(screen.getAllByRole('button', { name: /ban/i }).length).toBeGreaterThan(0) - }) + expect(screen.getByRole('heading', { name: /coming in p3/i })).toBeInTheDocument() + expect(screen.getByText(/planned for the p3\.2 milestone/i)).toBeInTheDocument() }) it('passes axe accessibility check', async () => { const { container } = render() - await waitFor(() => { - expect(screen.getByText('Jay Admin')).toBeInTheDocument() - }) const results = await axe(container) expect(results).toHaveNoViolations() }) diff --git a/src/app/admin/users/page.tsx b/src/app/admin/users/page.tsx index d38288c..326c526 100644 --- a/src/app/admin/users/page.tsx +++ b/src/app/admin/users/page.tsx @@ -2,110 +2,27 @@ * Admin user management page. * URL: /admin/users * User list with ban controls and cross-community ban warnings. + * Backend endpoint not yet implemented (planned for P3.2). * @see specs/prd-web.md Section M11 */ -'use client' - -import { useState, useEffect, useCallback } from 'react' +import { Users } from '@phosphor-icons/react/dist/ssr' import { AdminLayout } from '@/components/admin/admin-layout' -import { ErrorAlert } from '@/components/error-alert' -import { UserCard } from '@/components/admin/users/user-card' -import { getAdminUsers, banUser, unbanUser } from '@/lib/api/client' -import type { AdminUser } from '@/lib/api/types' -import { useAuth } from '@/hooks/use-auth' -import { useToast } from '@/hooks/use-toast' export default function AdminUsersPage() { - const { getAccessToken } = useAuth() - const { toast } = useToast() - const [users, setUsers] = useState([]) - const [loading, setLoading] = useState(true) - const [loadError, setLoadError] = useState(null) - const [actionError, setActionError] = useState(null) - - const fetchUsers = useCallback(async () => { - setLoadError(null) - try { - const response = await getAdminUsers(getAccessToken() ?? '') - setUsers(response.users) - } catch { - setLoadError('Failed to load users. The API may be unreachable.') - } finally { - setLoading(false) - } - }, [getAccessToken]) - - useEffect(() => { - void fetchUsers() - }, [fetchUsers]) - - const handleBan = async (did: string) => { - setActionError(null) - try { - await banUser(did, 'Banned by admin', getAccessToken() ?? '') - setUsers((prev) => - prev.map((u) => - u.did === did - ? { - ...u, - isBanned: true, - bannedAt: new Date().toISOString(), - banReason: 'Banned by admin', - } - : u - ) - ) - toast({ title: 'User banned' }) - } catch { - setActionError('Failed to ban user. Please try again.') - } - } - - const handleUnban = async (did: string) => { - setActionError(null) - try { - await unbanUser(did, getAccessToken() ?? '') - setUsers((prev) => - prev.map((u) => - u.did === did ? { ...u, isBanned: false, bannedAt: null, banReason: null } : u - ) - ) - toast({ title: 'User unbanned' }) - } catch { - setActionError('Failed to unban user. Please try again.') - } - } - return (

User management

- {loadError && ( - void fetchUsers()} /> - )} - - {actionError && setActionError(null)} />} - - {loading &&

Loading users...

} - - {!loading && users.length === 0 && ( -

No users found.

- )} - - {!loading && users.length > 0 && ( -
- {users.map((user) => ( - void handleBan(did)} - onUnban={(did) => void handleUnban(did)} - /> - ))} -
- )} +
+ +

Coming in P3

+

+ User management (search, role assignment, ban controls) is planned for the P3.2 + milestone. The admin API endpoint is not yet available. +

+
)