import { useState } from 'react'; import { Link } from 'react-router-dom'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { authListRoles, authCreateRole, type AuthRole } from '../../api/client'; import { useAuthMe } from '../../hooks/useAuthMe'; import PageHeader from '../../components/PageHeader'; import ErrorState from '../../components/ErrorState'; import { STALE_TIME } from '../../api/queryConstants'; // ============================================================================= // Bundle 1 Phase 10 — RolesPage. // // Lists every role in the active tenant. Render-time permission gating: // // - The "Create role" button is HIDDEN when the caller lacks // auth.role.create. Server-side enforcement still 403s an // end-run; the hide is UX, not security. // - Every row links to /auth/roles/:id; that page in turn gates // the edit / delete / add-permission affordances. // // data-testid attributes flag every interactive element so the future // E2E suite (Playwright or equivalent) can assert behaviour without // brittle CSS selectors. // ============================================================================= interface CreateRoleModalProps { isOpen: boolean; onClose: () => void; onSuccess: () => void; } function CreateRoleModal({ isOpen, onClose, onSuccess }: CreateRoleModalProps) { const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const [dirty, setDirty] = useState(false); if (!isOpen) return null; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) return; setSubmitting(true); setError(null); try { await authCreateRole({ name: name.trim(), description: description.trim() }); setName(''); setDescription(''); setDirty(false); onSuccess(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setSubmitting(false); } }; const handleClose = () => { if (dirty && !window.confirm('Discard unsaved changes?')) return; setName(''); setDescription(''); setDirty(false); setError(null); onClose(); }; return (
e.stopPropagation()} data-testid="create-role-modal" >

Create role

{error && (
{error}
)}
{ setName(e.target.value); setDirty(true); }} className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm" placeholder="release-manager" required data-testid="create-role-name" />