Conversation
Add query hooks (useAdminNamespaces, useAdminNamespace) and mutation hooks (useEditNamespace, useDeleteNamespace) for admin namespace CRUD, mirroring the admin user hooks.
Add paginated namespace list with search, detail page with properties/settings/members, edit drawer, delete dialog with cascade warning, and routes under LicenseGuard.
Add 52 tests covering EditNamespaceDrawer (form pre-filling, validation, submit payload, 409/generic errors, state reset) and DeleteNamespaceDialog (cascade warning, mutation calls, callbacks, error handling).
|
Claude finished @luizhf42's task in 1m 1s —— View job Code Review CompleteReviewed 10 files across code quality, security, testing, TypeScript/React patterns, and architecture. 3 inline issues posted (1 critical, 1 high, 1 medium).
Additional notesThe routes are correctly guarded ( If you've addressed the feedback and want a new review, tag |
| vi.mock("../../../../components/common/Drawer", async () => ({ | ||
| default: (await import("../../users/__tests__/mocks")).MockDrawer, | ||
| })); |
There was a problem hiding this comment.
critical: This imports MockDrawer from ../../users/__tests__/mocks, but that file does not exist anywhere in the codebase — no pages/admin/users/ directory exists, and MockDrawer is never exported from any module. The entire test suite (all 26 tests) will fail at import resolution.
Either create the shared mock at the expected path, or inline the mock directly in this file (following the pattern used in DeleteNamespaceDialog.test.tsx for ConfirmDialog):
| vi.mock("../../../../components/common/Drawer", async () => ({ | |
| default: (await import("../../users/__tests__/mocks")).MockDrawer, | |
| })); | |
| vi.mock("../../../../components/common/Drawer", () => ({ | |
| default: ({ | |
| open, | |
| onClose, | |
| title, | |
| subtitle, | |
| children, | |
| footer, | |
| }: { | |
| open: boolean; | |
| onClose: () => void; | |
| title: string; | |
| subtitle?: React.ReactNode; | |
| children: React.ReactNode; | |
| footer?: React.ReactNode; | |
| }) => { | |
| if (!open) return null; | |
| return ( | |
| <div> | |
| <h2>{title}</h2> | |
| {subtitle && <div>{subtitle}</div>} | |
| <div>{children}</div> | |
| {footer && <div>{footer}</div>} | |
| </div> | |
| ); | |
| }, | |
| })); |
| } from "../client/@tanstack/react-query.gen"; | ||
| import { useInvalidateByIds } from "./useInvalidateQueries"; | ||
|
|
||
| export function useEditNamespace() { |
There was a problem hiding this comment.
high: useEditNamespace and useDeleteNamespace are also exported from hooks/useNamespaceMutations.ts with identical names but very different behavior — the non-admin useDeleteNamespace logs the user out and redirects to /login, while this admin version invalidates admin query caches. IDE auto-import could silently suggest the wrong one.
Consider prefixing with Admin to make the scope explicit:
| export function useEditNamespace() { | |
| export function useAdminEditNamespace() { |
Same applies to useDeleteNamespace on line 19 → useAdminDeleteNamespace. The three import sites (EditNamespaceDrawer.tsx, DeleteNamespaceDialog.tsx, and their tests) would need updating accordingly.
|
|
||
| const ownerMember = namespace.members?.find((m) => m.id === namespace.owner); | ||
| const ownerLabel = ownerMember?.email || namespace.owner; | ||
| const namespaceType = ownerMember?.type; |
There was a problem hiding this comment.
medium: ownerMember?.type reads a field that doesn't exist on members. The backend Member struct has id, email, role, and added_at — no type field. The type field (personal/team) lives on the Namespace struct itself. This means namespaceType is always undefined and the badge on lines 97–101 never renders (dead code).
If you want to show the namespace type:
| const namespaceType = ownerMember?.type; | |
| const namespaceType = (namespace as Record<string, unknown>).type as string | undefined; |
Or use namespace.type directly if the generated client type includes it. If the field isn't available yet in the OpenAPI spec, remove lines 62 and 97–101 until it is.
What
Admin namespace management for the React UI — paginated list, detail page, inline edit, and delete with cascade warning.
Why
Part of the admin panel migration from Vue to React (shellhub-io/team#93, feature #6). Depends on shellhub-io/cloud#2328 for the backend
/admin/api/namespacesendpoints.Changes
useAdminNamespaces(paginated list with base64-encoded name filter),useAdminNamespace(single fetch),useEditNamespaceanduseDeleteNamespacemutations with cache invalidation — all mirroring the admin user hooks pattern/admin/namespacesand/admin/namespaces/:idunderLicenseGuardTesting
52 tests covering EditNamespaceDrawer (form pre-filling, validation, submit payload structure, 409/generic errors, state reset on reopen) and DeleteNamespaceDialog (cascade warning, mutation calls, callbacks, error handling).
Backend dependency: requires shellhub-io/cloud#2328 merged for
/admin/api/namespacesto return 200 instead of 404.