From 2bd358049b16192f240ba5662c900773ea51b7ef Mon Sep 17 00:00:00 2001
From: dongmucat <1127093059@qq.com>
Date: Mon, 27 Jul 2026 16:37:10 +0800
Subject: [PATCH] fix(frontend): bound namespace selection
Signed-off-by: dongmucat <1127093059@qq.com>
---
.../my-namespaces-super-admin-actions.spec.ts | 17 +++++++
.../namespace/use-my-namespaces.test.ts | 17 -------
.../features/namespace/use-my-namespaces.ts | 5 ---
web/src/pages/dashboard/my-namespaces.test.ts | 1 -
web/src/pages/dashboard/my-skills.test.ts | 16 ++++++-
web/src/pages/dashboard/my-skills.tsx | 34 +++++---------
.../pages/dashboard/namespace-members.test.ts | 16 ++++++-
web/src/pages/dashboard/namespace-members.tsx | 6 +--
web/src/pages/dashboard/publish.test.ts | 23 +++++++---
web/src/pages/dashboard/publish.tsx | 44 +++++++------------
web/src/pages/dashboard/reviews.test.ts | 16 ++++---
web/src/pages/dashboard/reviews.tsx | 10 +++--
.../components/namespace-picker.test.tsx | 17 +++++++
.../shared/components/namespace-picker.tsx | 18 +++++++-
web/src/shared/components/user-menu.test.tsx | 26 +++++++----
web/src/shared/components/user-menu.tsx | 10 +++--
.../hooks/use-namespace-queries.test.ts | 28 ------------
web/src/shared/hooks/use-namespace-queries.ts | 27 ------------
18 files changed, 167 insertions(+), 164 deletions(-)
delete mode 100644 web/src/features/namespace/use-my-namespaces.test.ts
delete mode 100644 web/src/features/namespace/use-my-namespaces.ts
diff --git a/web/e2e/my-namespaces-super-admin-actions.spec.ts b/web/e2e/my-namespaces-super-admin-actions.spec.ts
index c8286402..904be64a 100644
--- a/web/e2e/my-namespaces-super-admin-actions.spec.ts
+++ b/web/e2e/my-namespaces-super-admin-actions.spec.ts
@@ -23,6 +23,7 @@ test.describe('My Namespaces super admin actions (Real API)', () => {
try {
adminBuilder = new E2eTestDataBuilder(page, testInfo)
await adminBuilder.init()
+ const activeNamespace = await adminBuilder.createNamespace('e2e-super-admin-publish-active')
const namespace = await adminBuilder.createNamespace('e2e-super-admin-read')
namespaceSlug = namespace.slug
@@ -72,6 +73,22 @@ test.describe('My Namespaces super admin actions (Real API)', () => {
expect(archiveResponse.ok()).toBe(true)
namespaceArchived = true
+ await page.goto('/dashboard/publish')
+ await page.getByRole('button', { name: 'Select namespace', exact: true }).click()
+ await page.getByRole('searchbox', { name: 'Search namespaces' }).fill(activeNamespace.slug)
+ const activeOption = page.getByRole('button', {
+ name: `${activeNamespace.displayName} (@${activeNamespace.slug})`,
+ })
+ await expect(activeOption).toBeVisible()
+ await activeOption.click()
+
+ await page.getByRole('button', { name: `@${activeNamespace.slug}`, exact: true }).click()
+ await page.getByRole('searchbox', { name: 'Search namespaces' }).fill(namespace.slug)
+ await expect(page.getByText('No namespaces found')).toBeVisible()
+ await expect(page.getByRole('button', {
+ name: `${namespace.displayName} (@${namespace.slug})`,
+ })).toHaveCount(0)
+
await page.goto('/dashboard/namespaces')
const namespaceCard = page.getByTestId(`namespace-card-${namespace.slug}`)
diff --git a/web/src/features/namespace/use-my-namespaces.test.ts b/web/src/features/namespace/use-my-namespaces.test.ts
deleted file mode 100644
index ee36c691..00000000
--- a/web/src/features/namespace/use-my-namespaces.test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import * as mod from './use-my-namespaces'
-
-/**
- * use-my-namespaces.ts is a feature-local re-export of the
- * useMyNamespaces hook from the shared query layer. There is no custom
- * logic, transformation, or query-key function to test.
- *
- * We verify the re-export contract so import paths used by namespace
- * dashboard screens break fast if the module shape changes.
- */
-describe('use-my-namespaces re-export', () => {
- it('re-exports useMyNamespaces as a function', () => {
- expect(mod.useMyNamespaces).toBeDefined()
- expect(typeof mod.useMyNamespaces).toBe('function')
- })
-})
diff --git a/web/src/features/namespace/use-my-namespaces.ts b/web/src/features/namespace/use-my-namespaces.ts
deleted file mode 100644
index 6332cb17..00000000
--- a/web/src/features/namespace/use-my-namespaces.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-/**
- * Preserves a feature-local import path for dashboard namespace screens while
- * the underlying query implementation still lives in the shared hook module.
- */
-export { useMyNamespaces } from '@/shared/hooks/use-namespace-queries'
diff --git a/web/src/pages/dashboard/my-namespaces.test.ts b/web/src/pages/dashboard/my-namespaces.test.ts
index e57790a9..8277cd1b 100644
--- a/web/src/pages/dashboard/my-namespaces.test.ts
+++ b/web/src/pages/dashboard/my-namespaces.test.ts
@@ -82,7 +82,6 @@ vi.mock('@/shared/hooks/use-namespace-queries', () => ({
useArchiveNamespace: () => ({ mutateAsync: archiveMutateAsync }),
useDeleteNamespace: () => ({ mutateAsync: deleteMutateAsync }),
useFreezeNamespace: () => ({ mutateAsync: freezeMutateAsync }),
- useMyNamespaces: () => ({ data: mockNamespaces, isLoading: false }),
useMyNamespacesPage: () => ({
data: mockNamespacePage.total > 0 || mockNamespacePage.items.length > 0
? mockNamespacePage
diff --git a/web/src/pages/dashboard/my-skills.test.ts b/web/src/pages/dashboard/my-skills.test.ts
index c82faf93..92fa70dc 100644
--- a/web/src/pages/dashboard/my-skills.test.ts
+++ b/web/src/pages/dashboard/my-skills.test.ts
@@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
const navigateMock = vi.fn()
const buttonRecords: Array<{ label: string; onClick?: ((event?: { stopPropagation: () => void }) => void) | undefined }> = []
const useMySkillsMock = vi.fn()
+const useMyNamespacesPageMock = vi.fn()
vi.mock('@tanstack/react-router', () => ({
useNavigate: () => navigateMock,
@@ -72,7 +73,7 @@ vi.mock('@/shared/hooks/use-user-queries', () => ({
}))
vi.mock('@/shared/hooks/use-namespace-queries', () => ({
- useMyNamespaces: () => ({ data: [] }),
+ useMyNamespacesPage: (...args: unknown[]) => useMyNamespacesPageMock(...args),
}))
vi.mock('@/shared/hooks/use-debounce', () => ({
@@ -114,6 +115,13 @@ describe('MySkillsPage', () => {
beforeEach(() => {
navigateMock.mockReset()
buttonRecords.length = 0
+ useMyNamespacesPageMock.mockReset()
+ useMyNamespacesPageMock.mockReturnValue({
+ data: { items: [], total: 0, page: 0, size: 20 },
+ isLoading: false,
+ error: null,
+ refetch: vi.fn(),
+ })
useMySkillsMock.mockReturnValue({
data: {
items: [
@@ -137,6 +145,12 @@ describe('MySkillsPage', () => {
})
})
+ it('loads only the first namespace picker page while the picker is closed', () => {
+ renderToStaticMarkup(createElement(MySkillsPage))
+
+ expect(useMyNamespacesPageMock).toHaveBeenCalledWith({ page: 0, size: 20 }, false)
+ })
+
it('navigates to publish page with namespace and visibility when update is clicked', () => {
renderToStaticMarkup(createElement(MySkillsPage))
diff --git a/web/src/pages/dashboard/my-skills.tsx b/web/src/pages/dashboard/my-skills.tsx
index 4dbe4765..0c70eac7 100644
--- a/web/src/pages/dashboard/my-skills.tsx
+++ b/web/src/pages/dashboard/my-skills.tsx
@@ -5,13 +5,12 @@ import { useAuth } from '@/features/auth/use-auth'
import { Button } from '@/shared/ui/button'
import { Card } from '@/shared/ui/card'
import { Input } from '@/shared/ui/input'
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
import { EmptyState } from '@/shared/components/empty-state'
import { ConfirmDialog } from '@/shared/components/confirm-dialog'
import { DashboardPageHeader } from '@/shared/components/dashboard-page-header'
+import { NamespacePicker } from '@/shared/components/namespace-picker'
import { Pagination } from '@/shared/components/pagination'
import { useArchiveSkill, useUnarchiveSkill, useWithdrawSkillReview } from '@/shared/hooks/use-skill-queries'
-import { useMyNamespaces } from '@/shared/hooks/use-namespace-queries'
import { useMySkills, useSubmitPromotion } from '@/shared/hooks/use-user-queries'
import { useDebounce } from '@/shared/hooks/use-debounce'
import { getHeadlineVersion, getPublishedVersion, getOwnerPreviewVersion, hasPendingOwnerPreview } from '@/shared/lib/skill-lifecycle'
@@ -22,8 +21,6 @@ import { ApiError } from '@/api/client'
import { getMySkillEmptyStateKey, getMySkillFilters, type MySkillFilter } from './my-skill-filters'
const PAGE_SIZE = 10
-const ALL_NAMESPACES_VALUE = '__all_namespaces__'
-
/**
* Dashboard page for skills owned by the current user.
*
@@ -91,8 +88,6 @@ export function MySkillsPage() {
q: keyword || undefined,
namespace: namespaceFilter || undefined,
})
- const { data: namespaceOptions } = useMyNamespaces()
-
const skills = skillPage?.items ?? []
const totalPages = skillPage ? Math.max(Math.ceil(skillPage.total / skillPage.size), 1) : 1
const availableFilters = getMySkillFilters(hasRole('SUPER_ADMIN'))
@@ -302,24 +297,15 @@ export function MySkillsPage() {
aria-label={t('mySkills.searchPlaceholder')}
className="sm:max-w-md"
/>
-
+
+ {
+ updateSearch({ namespace: value || undefined, page: 0 })
+ }}
+ emptyValueLabel={t('mySkills.namespaceFilterAll')}
+ />
+
{hasActiveSearch ? (