From 4ef0f5c8edf4d73833b62a3233aa9c592a7432c1 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Fri, 11 Sep 2026 15:50:39 +0800 Subject: [PATCH] feat(web): reorganize marketplace and console navigation Made-with: Proma Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- docker-compose.staging.yml | 2 ++ docs/08-frontend-architecture.md | 3 +++ web/e2e/dashboard-shell.spec.ts | 2 ++ web/e2e/theme-toggle.spec.ts | 2 +- web/src/app/layout.tsx | 6 ++---- web/src/i18n/locales/en.json | 5 +++++ web/src/i18n/locales/ru.json | 5 +++++ web/src/i18n/locales/zh.json | 5 +++++ web/src/pages/dashboard.test.tsx | 2 ++ web/src/pages/dashboard.tsx | 4 +++- web/src/shared/components/user-menu.test.tsx | 17 +++++++++++++++++ web/src/shared/components/user-menu.tsx | 6 ++++++ 12 files changed, 53 insertions(+), 6 deletions(-) diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml index 81a5bc6d..a9e6199d 100644 --- a/docker-compose.staging.yml +++ b/docker-compose.staging.yml @@ -46,6 +46,8 @@ services: SKILLHUB_SECURITY_SCANNER_ENABLED: "true" SKILLHUB_SECURITY_SCANNER_URL: http://skill-scanner:8000 SKILLHUB_SECURITY_SCANNER_MODE: upload + # Local staging needs typed Suite review writes so public Suite market fixtures can be submitted and approved. + SKILLHUB_SUITE_REVIEW_WRITES_ENABLED: "true" depends_on: postgres: condition: service_healthy diff --git a/docs/08-frontend-architecture.md b/docs/08-frontend-architecture.md index 681d24bc..972f45a4 100644 --- a/docs/08-frontend-architecture.md +++ b/docs/08-frontend-architecture.md @@ -28,6 +28,8 @@ |------|------|------| | 首页 | `/` | 精选/热门/最新、搜索入口 | | 搜索页 | `/search` | 关键词搜索 + 过滤 + 排序 | +| 技能套件市场 | `/suites` | 浏览和搜索已发布的技能套件 | +| 技能套件详情 | `/suite/{namespace}/{slug}` | 套件说明、成员技能和安装计划 | | 命名空间主页 | `/@{namespace}` | 空间介绍 + 技能列表 | | 技能详情页 | `/@{namespace}/{slug}` | README 渲染、版本、评分、收藏、下载 | | 版本历史 | `/@{namespace}/{slug}/versions` | 版本列表 + changelog | @@ -39,6 +41,7 @@ | 页面 | 路径 | 说明 | |------|------|------| | 我的技能 | `/dashboard/skills` | 我发布的技能 + 统一生命周期状态 | +| 我的技能套件 | `/dashboard/suites` | 创建和管理我维护的技能套件 | | 发布技能 | `/dashboard/publish` | zip 上传 + 预览 + 提交审核 | | 我的收藏 | `/dashboard/stars` | 收藏列表 | | Token 管理 | `/dashboard/tokens` | 创建/查看/吊销 | diff --git a/web/e2e/dashboard-shell.spec.ts b/web/e2e/dashboard-shell.spec.ts index d16b86b1..6aba4821 100644 --- a/web/e2e/dashboard-shell.spec.ts +++ b/web/e2e/dashboard-shell.spec.ts @@ -15,7 +15,9 @@ test.describe('Dashboard Shell (Real API)', () => { const sidebar = page.getByRole('complementary') await expect(sidebar.getByRole('link', { name: 'Profile', exact: true })).toBeVisible() await expect(sidebar.getByRole('link', { name: 'My Skills', exact: true })).toBeVisible() + await expect(sidebar.getByRole('link', { name: 'My Suites', exact: true })).toBeVisible() await expect(sidebar.getByRole('link', { name: 'API Tokens', exact: true })).toBeVisible() await expect(page.getByText('View and manage all your published skills')).toBeVisible() + await expect(page.getByText('View and manage your skill suites')).toBeVisible() }) }) diff --git a/web/e2e/theme-toggle.spec.ts b/web/e2e/theme-toggle.spec.ts index 556df646..0be5ca36 100644 --- a/web/e2e/theme-toggle.spec.ts +++ b/web/e2e/theme-toggle.spec.ts @@ -166,7 +166,7 @@ test.describe('Light and dark theme', () => { window as Window & { __themeAtFirstReactContent?: boolean } ).__themeAtFirstReactContent)).toBe(true) - await page.getByRole('link', { name: 'Search', exact: true }).first().click() + await page.getByRole('link', { name: 'Skill Marketplace', exact: true }).first().click() await expect(page).toHaveURL(/\/search(?:\?|$)/) await expect(page.locator('html')).toHaveClass(/dark/) await expect(page.getByPlaceholder('Search skills...')).toBeVisible() diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index 3d6c638f..164582d2 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -84,11 +84,9 @@ export function Layout() { }> = [ { label: t('nav.landing'), to: '/', exact: true }, { label: t('nav.publish'), to: '/dashboard/publish', auth: true }, - { label: t('nav.search'), to: '/search' }, - { label: t('nav.suites', { defaultValue: '技能套件' }), to: '/suites' }, + { label: t('nav.marketplace'), to: '/search' }, + { label: t('nav.suites'), to: '/suites' }, { label: t('nav.dashboard'), to: '/dashboard', auth: true }, - { label: t('nav.mySkills'), to: '/dashboard/skills', auth: true }, - { label: t('nav.mySuites'), to: '/dashboard/suites', auth: true }, ] const isActive = (to: string, exact?: boolean) => { diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index bc5ef0a0..dd1facca 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -13,6 +13,7 @@ "landing": "Home", "home": "Skill Center", "search": "Search", + "marketplace": "Skill Marketplace", "skillDetail": "Skill Detail", "explore": "Explore Skills", "dashboard": "Dashboard", @@ -471,6 +472,7 @@ "notifications": "Notifications", "skillsAndData": "Skills & Data", "mySkills": "My Skills", + "mySuites": "My Suites", "publish": "Publish", "namespaces": "Namespaces", "stars": "My Stars", @@ -484,6 +486,8 @@ "overview": { "mySkills": "My Skills", "mySkillsDesc": "View and manage all your published skills", + "mySuites": "My Suites", + "mySuitesDesc": "View and manage your skill suites", "publish": "Publish", "publishDesc": "Upload and publish a new skill", "tokens": "API Tokens", @@ -1559,6 +1563,7 @@ "menu": { "dashboard": "Dashboard", "mySkills": "My Skills", + "mySuites": "My Suites", "myNamespaces": "My Namespaces", "governance": "Governance Center", "stars": "Starred", diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index 13585d05..c7a1c8c9 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -13,6 +13,7 @@ "landing": "Главная", "home": "Центр скиллов", "search": "Поиск", + "marketplace": "Каталог навыков", "skillDetail": "Карточка скилла", "explore": "Обзор скиллов", "dashboard": "Панель управления", @@ -471,6 +472,7 @@ "notifications": "Уведомления", "skillsAndData": "Скиллы и данные", "mySkills": "Мои скиллы", + "mySuites": "Мои наборы", "publish": "Публикация", "namespaces": "Пространства", "stars": "Мои звёзды", @@ -484,6 +486,8 @@ "overview": { "mySkills": "Мои скиллы", "mySkillsDesc": "Просмотр и управление опубликованными скиллами", + "mySuites": "Мои наборы", + "mySuitesDesc": "Просмотр и управление наборами навыков", "publish": "Публикация", "publishDesc": "Загрузить и опубликовать новый скилл", "tokens": "Токены API", @@ -1878,6 +1882,7 @@ "menu": { "dashboard": "Панель", "mySkills": "Мои скиллы", + "mySuites": "Мои наборы", "myNamespaces": "Мои пространства имён", "namespacesAdmin": "Управление пространствами имён", "governance": "Центр управления", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index c8eb9550..681e3545 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -13,6 +13,7 @@ "landing": "首页", "home": "技能中心", "search": "搜索", + "marketplace": "技能市场", "skillDetail": "技能详情", "explore": "探索", "dashboard": "控制台", @@ -471,6 +472,7 @@ "notifications": "通知设置", "skillsAndData": "技能与数据", "mySkills": "我的技能", + "mySuites": "我的套件", "publish": "技能发布", "namespaces": "命名空间", "stars": "我的收藏", @@ -484,6 +486,8 @@ "overview": { "mySkills": "我的技能", "mySkillsDesc": "查看和管理你发布的所有技能", + "mySuites": "我的套件", + "mySuitesDesc": "查看和管理你的技能套件", "publish": "技能发布", "publishDesc": "上传并发布新的技能到平台", "tokens": "Token 凭证", @@ -1558,6 +1562,7 @@ "menu": { "dashboard": "控制台", "mySkills": "我的技能", + "mySuites": "我的套件", "myNamespaces": "我的命名空间", "governance": "治理中心", "stars": "我的收藏", diff --git a/web/src/pages/dashboard.test.tsx b/web/src/pages/dashboard.test.tsx index acea151d..24584794 100644 --- a/web/src/pages/dashboard.test.tsx +++ b/web/src/pages/dashboard.test.tsx @@ -67,6 +67,8 @@ describe('DashboardPage', () => { expect(html).toContain('sidebar.skillsAndData') expect(html).toContain('overview.mySkills') + expect(html).toContain('sidebar.mySuites') + expect(html).toContain('overview.mySuites') expect(html).not.toContain('overview.publish') }) }) diff --git a/web/src/pages/dashboard.tsx b/web/src/pages/dashboard.tsx index 4944e236..e2df560f 100644 --- a/web/src/pages/dashboard.tsx +++ b/web/src/pages/dashboard.tsx @@ -5,7 +5,7 @@ import { canViewGovernanceCenter } from '@/shared/lib/governance-access' import { APP_SHELL_PAGE_CLASS_NAME } from '@/app/page-shell-style' import { DashboardPageHeader } from '@/shared/components/dashboard-page-header' import { - Star, Heart, Package, Key, Shield, Flag, Globe, + Star, Heart, Package, Boxes, Key, Shield, Flag, Globe, UserCog, Lock, Bell, Clock, ChevronRight, } from 'lucide-react' @@ -44,6 +44,7 @@ export const SIDEBAR_GROUPS: SidebarGroup[] = [ label: 'sidebar.skillsAndData', items: [ { key: 'skills', icon: Package, label: 'sidebar.mySkills', to: '/dashboard/skills' }, + { key: 'suites', icon: Boxes, label: 'sidebar.mySuites', to: '/dashboard/suites' }, { key: 'namespaces', icon: Globe, label: 'sidebar.namespaces', to: '/dashboard/namespaces' }, { key: 'stars', icon: Star, label: 'sidebar.stars', to: '/dashboard/stars' }, { key: 'subscriptions', icon: Heart, label: 'sidebar.subscriptions', to: '/dashboard/subscriptions' }, @@ -79,6 +80,7 @@ export const SIDEBAR_NAV = SIDEBAR_NAV_ITEMS.map(({ key, icon, label, to, admin, */ const OVERVIEW_CARDS = [ { key: 'skills', icon: Package, label: 'overview.mySkills', to: '/dashboard/skills', desc: 'overview.mySkillsDesc' }, + { key: 'suites', icon: Boxes, label: 'overview.mySuites', to: '/dashboard/suites', desc: 'overview.mySuitesDesc' }, { key: 'tokens', icon: Key, label: 'overview.tokens', to: '/dashboard/tokens', desc: 'overview.tokensDesc' }, { key: 'stars', icon: Star, label: 'overview.stars', to: '/dashboard/stars', desc: 'overview.starsDesc' }, { key: 'profile', icon: UserCog, label: 'overview.profile', to: '/settings/profile', desc: 'overview.profileDesc' }, diff --git a/web/src/shared/components/user-menu.test.tsx b/web/src/shared/components/user-menu.test.tsx index b5ac866b..ed8d1b2c 100644 --- a/web/src/shared/components/user-menu.test.tsx +++ b/web/src/shared/components/user-menu.test.tsx @@ -77,6 +77,23 @@ describe('user-menu module exports', () => { }) describe('UserMenu navigation', () => { + it('lists personal destinations in the requested order', () => { + const html = renderToStaticMarkup( + , + ) + + expect(html).toContain('user.menu.mySkills') + expect(html).toContain('user.menu.mySuites') + expect(html).toContain('user.menu.dashboard') + expect(html.indexOf('user.menu.mySkills')).toBeLessThan(html.indexOf('user.menu.mySuites')) + expect(html.indexOf('user.menu.mySuites')).toBeLessThan(html.indexOf('user.menu.dashboard')) + }) + it('keeps dashboard-only personal links out of the compact avatar menu', () => { const html = renderToStaticMarkup( + + {t('user.menu.mySkills')} + + + {t('user.menu.mySuites')} + {t('user.menu.dashboard')}