feat: RBAC page reads cmd-k navigation state for tab switch and highlight
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Badge,
|
||||
@@ -35,7 +35,7 @@ import styles from './UserManagement.module.css';
|
||||
|
||||
const BUILTIN_ADMINS_ID = '00000000-0000-0000-0000-000000000010';
|
||||
|
||||
export default function GroupsTab() {
|
||||
export default function GroupsTab({ highlightId, onHighlightConsumed }: { highlightId?: string | null; onHighlightConsumed?: () => void }) {
|
||||
const { toast } = useToast();
|
||||
const { data: groups = [], isLoading: groupsLoading } = useGroups();
|
||||
const { data: users = [] } = useUsers();
|
||||
@@ -47,6 +47,17 @@ export default function GroupsTab() {
|
||||
const [deleteTarget, setDeleteTarget] = useState<GroupDetail | null>(null);
|
||||
const [removeRoleTarget, setRemoveRoleTarget] = useState<string | null>(null);
|
||||
|
||||
// Auto-select highlighted item from cmd-k navigation
|
||||
useEffect(() => {
|
||||
if (highlightId && groups) {
|
||||
const match = groups.find((g) => g.id === highlightId);
|
||||
if (match) {
|
||||
setSelectedId(match.id);
|
||||
onHighlightConsumed?.();
|
||||
}
|
||||
}
|
||||
}, [highlightId, groups]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Create form state
|
||||
const [newName, setNewName] = useState('');
|
||||
const [newParent, setNewParent] = useState('');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router';
|
||||
import { StatCard, Tabs } from '@cameleer/design-system';
|
||||
import { useRbacStats } from '../../api/queries/admin/rbac';
|
||||
import styles from './UserManagement.module.css';
|
||||
@@ -12,9 +13,30 @@ const TABS = [
|
||||
{ label: 'Roles', value: 'roles' },
|
||||
];
|
||||
|
||||
const VALID_TABS = new Set(TABS.map((t) => t.value));
|
||||
|
||||
export default function RbacPage() {
|
||||
const { data: stats } = useRbacStats();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [tab, setTab] = useState('users');
|
||||
const [highlightId, setHighlightId] = useState<string | null>(null);
|
||||
|
||||
// Read tab + highlight from location state (set by cmd-k navigation)
|
||||
useEffect(() => {
|
||||
const state = location.state as { tab?: string; highlight?: string } | null;
|
||||
if (!state) return;
|
||||
|
||||
if (state.tab && VALID_TABS.has(state.tab)) {
|
||||
setTab(state.tab);
|
||||
}
|
||||
if (state.highlight) {
|
||||
setHighlightId(state.highlight);
|
||||
}
|
||||
|
||||
// Consume the state so back-navigation doesn't re-trigger
|
||||
navigate(location.pathname, { replace: true, state: null });
|
||||
}, [location.state]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -23,11 +45,11 @@ export default function RbacPage() {
|
||||
<StatCard label="Groups" value={stats?.groupCount ?? 0} />
|
||||
<StatCard label="Roles" value={stats?.roleCount ?? 0} />
|
||||
</div>
|
||||
<Tabs tabs={TABS} active={tab} onChange={setTab} />
|
||||
<Tabs tabs={TABS} active={tab} onChange={(v) => { setTab(v); setHighlightId(null); }} />
|
||||
<div className={styles.tabContent}>
|
||||
{tab === 'users' && <UsersTab />}
|
||||
{tab === 'groups' && <GroupsTab />}
|
||||
{tab === 'roles' && <RolesTab />}
|
||||
{tab === 'users' && <UsersTab highlightId={highlightId} onHighlightConsumed={() => setHighlightId(null)} />}
|
||||
{tab === 'groups' && <GroupsTab highlightId={highlightId} onHighlightConsumed={() => setHighlightId(null)} />}
|
||||
{tab === 'roles' && <RolesTab highlightId={highlightId} onHighlightConsumed={() => setHighlightId(null)} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Badge,
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
import type { RoleDetail } from '../../api/queries/admin/rbac';
|
||||
import styles from './UserManagement.module.css';
|
||||
|
||||
export default function RolesTab() {
|
||||
export default function RolesTab({ highlightId, onHighlightConsumed }: { highlightId?: string | null; onHighlightConsumed?: () => void }) {
|
||||
const { toast } = useToast();
|
||||
const { data: roles, isLoading } = useRoles();
|
||||
|
||||
@@ -31,6 +31,17 @@ export default function RolesTab() {
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [deleteTarget, setDeleteTarget] = useState<RoleDetail | null>(null);
|
||||
|
||||
// Auto-select highlighted item from cmd-k navigation
|
||||
useEffect(() => {
|
||||
if (highlightId && roles) {
|
||||
const match = roles.find((r) => r.id === highlightId);
|
||||
if (match) {
|
||||
setSelectedId(match.id);
|
||||
onHighlightConsumed?.();
|
||||
}
|
||||
}
|
||||
}, [highlightId, roles]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Create form state
|
||||
const [newName, setNewName] = useState('');
|
||||
const [newDesc, setNewDesc] = useState('');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Badge,
|
||||
@@ -36,7 +36,7 @@ import type { UserDetail } from '../../api/queries/admin/rbac';
|
||||
import { useAuthStore } from '../../auth/auth-store';
|
||||
import styles from './UserManagement.module.css';
|
||||
|
||||
export default function UsersTab() {
|
||||
export default function UsersTab({ highlightId, onHighlightConsumed }: { highlightId?: string | null; onHighlightConsumed?: () => void }) {
|
||||
const { toast } = useToast();
|
||||
const { data: users, isLoading } = useUsers();
|
||||
const { data: allGroups } = useGroups();
|
||||
@@ -49,6 +49,17 @@ export default function UsersTab() {
|
||||
const [deleteTarget, setDeleteTarget] = useState<UserDetail | null>(null);
|
||||
const [removeGroupTarget, setRemoveGroupTarget] = useState<string | null>(null);
|
||||
|
||||
// Auto-select highlighted item from cmd-k navigation
|
||||
useEffect(() => {
|
||||
if (highlightId && users) {
|
||||
const match = users.find((u) => u.userId === highlightId);
|
||||
if (match) {
|
||||
setSelectedId(match.userId);
|
||||
onHighlightConsumed?.();
|
||||
}
|
||||
}
|
||||
}, [highlightId, users]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Create form state
|
||||
const [newUsername, setNewUsername] = useState('');
|
||||
const [newDisplay, setNewDisplay] = useState('');
|
||||
|
||||
Reference in New Issue
Block a user