diff --git a/apps/web/e2e/a11y.spec.ts b/apps/web/e2e/a11y.spec.ts index de33d80..7b232d4 100644 --- a/apps/web/e2e/a11y.spec.ts +++ b/apps/web/e2e/a11y.spec.ts @@ -60,3 +60,14 @@ test('user settings pass the axe WCAG A/AA scan', async ({ browser }) => { await expectClean(page, '/settings'); await context.close(); }); + +test('admin area passes the axe WCAG A/AA scan', async ({ browser }) => { + const context = await contextForUser(browser, BASE, 'fixture-admin'); + const page = await context.newPage(); + await page.goto('/admin'); + await page.waitForLoadState('networkidle'); + // Personenliste sichtbar, inkl. der Icon-Aktionen (issue #175). + await page.locator('.user-manager__table .user-row').first().waitFor(); + await expectClean(page, '/admin'); + await context.close(); +}); diff --git a/apps/web/src/pages/UserManager.tsx b/apps/web/src/pages/UserManager.tsx index ff94694..2c0c833 100644 --- a/apps/web/src/pages/UserManager.tsx +++ b/apps/web/src/pages/UserManager.tsx @@ -1,9 +1,11 @@ import type { AdminUserListView, AdminUserView } from '@dorfteich/shared'; import { keepPreviousData, useQuery } from '@tanstack/react-query'; -import { useState } from 'react'; +import { MailCheck, ShieldMinus, ShieldPlus, Trash2, UserCheck, UserX } from 'lucide-react'; +import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../auth/auth-context'; +import { IconButton } from '../components/IconButton'; import { apiDelete, apiGet, apiPatch, apiPost } from '../lib/api'; const PAGE_SIZE = 20; @@ -101,6 +103,12 @@ function UserRow({ }): React.JSX.Element { const { t } = useTranslation('users'); const [confirmingDelete, setConfirmingDelete] = useState(false); + const confirmRef = useRef(null); + // The icon button unmounts when the confirm step appears — hand focus over + // so keyboard users land on the confirmation instead of losing focus. + useEffect(() => { + if (confirmingDelete) confirmRef.current?.focus(); + }, [confirmingDelete]); const disabled = user.status === 'DISABLED'; return ( @@ -118,31 +126,20 @@ function UserRow({ {!isSelf && ( <> - {user.status === 'PENDING_VERIFICATION' && ( - + + )} - + {user.isSiteAdmin ? : } + + + void run(() => + apiPatch(`/admin/users/${user.id}/disabled`, { disabled: !disabled }), + ) + } + > + {disabled ? : } + {confirmingDelete ? ( ) : ( - + + )} )} diff --git a/apps/web/src/styles/base.css b/apps/web/src/styles/base.css index 30456f2..e242224 100644 --- a/apps/web/src/styles/base.css +++ b/apps/web/src/styles/base.css @@ -2985,7 +2985,7 @@ ul[data-type='task_list'] li p:last-of-type { .user-row__actions { display: flex; - flex-wrap: wrap; + align-items: center; gap: var(--space-2); }