feat(web): Phase 7.0 minimalist 5-pillar navigation
- Refactor sidebar to Nothing.tech visual compliance - Add defensive route stubs for /authorizations, /knowledge-base, /settings - Dynamic badge for pending approvals count - Ultra-minimal borders (0.5px), no shadows Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
29
apps/web/src/app/[locale]/authorizations/page.tsx
Normal file
29
apps/web/src/app/[locale]/authorizations/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Authorizations Page - 授權中心
|
||||
* ==============================
|
||||
* Phase 7.0: 防禦性路由佔位
|
||||
*
|
||||
* Nothing.tech 空態設計:
|
||||
* - 畫面正中央極簡文字
|
||||
* - 等寬字體 + 淺灰色
|
||||
*/
|
||||
|
||||
import { AppLayout } from '@/components/layout'
|
||||
|
||||
export default function AuthorizationsPage({
|
||||
params,
|
||||
}: {
|
||||
params: { locale: string }
|
||||
}) {
|
||||
return (
|
||||
<AppLayout locale={params.locale}>
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<p className="font-mono text-sm text-neutral-400 tracking-wider">
|
||||
[ 授權中心建置中 / AUTHORIZATIONS_MODULE_UNDER_CONSTRUCTION ]
|
||||
</p>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
29
apps/web/src/app/[locale]/knowledge-base/page.tsx
Normal file
29
apps/web/src/app/[locale]/knowledge-base/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Knowledge Base Page - 知識殿堂
|
||||
* ==============================
|
||||
* Phase 7.0: 防禦性路由佔位
|
||||
*
|
||||
* Nothing.tech 空態設計:
|
||||
* - 畫面正中央極簡文字
|
||||
* - 等寬字體 + 淺灰色
|
||||
*/
|
||||
|
||||
import { AppLayout } from '@/components/layout'
|
||||
|
||||
export default function KnowledgeBasePage({
|
||||
params,
|
||||
}: {
|
||||
params: { locale: string }
|
||||
}) {
|
||||
return (
|
||||
<AppLayout locale={params.locale}>
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<p className="font-mono text-sm text-neutral-400 tracking-wider">
|
||||
[ 知識殿堂建置中 / KNOWLEDGE_BASE_MODULE_UNDER_CONSTRUCTION ]
|
||||
</p>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
29
apps/web/src/app/[locale]/settings/page.tsx
Normal file
29
apps/web/src/app/[locale]/settings/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Settings Page - 系統設定
|
||||
* ========================
|
||||
* Phase 7.0: 防禦性路由佔位
|
||||
*
|
||||
* Nothing.tech 空態設計:
|
||||
* - 畫面正中央極簡文字
|
||||
* - 等寬字體 + 淺灰色
|
||||
*/
|
||||
|
||||
import { AppLayout } from '@/components/layout'
|
||||
|
||||
export default function SettingsPage({
|
||||
params,
|
||||
}: {
|
||||
params: { locale: string }
|
||||
}) {
|
||||
return (
|
||||
<AppLayout locale={params.locale}>
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<p className="font-mono text-sm text-neutral-400 tracking-wider">
|
||||
[ 系統設定建置中 / SETTINGS_MODULE_UNDER_CONSTRUCTION ]
|
||||
</p>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Sidebar - 高透光玻璃側邊欄
|
||||
* ==========================
|
||||
* CPO-102: Nothing.tech 明亮工業風側邊導航
|
||||
* Phase 2.5: lucide-react 純代碼視覺 (符合第七章憲法)
|
||||
* Sidebar - Phase 7.0 極簡五柱導航
|
||||
* =================================
|
||||
* Nothing.tech 視覺憲法:
|
||||
* - 純白背景 (bg-white)
|
||||
* - 極細右邊框 (border-r-[0.5px] border-neutral-200)
|
||||
* - 無陰影
|
||||
* - 單色圖示
|
||||
*
|
||||
* Features:
|
||||
* - backdrop-blur 毛玻璃效果
|
||||
* - 固定寬度 (w-64)
|
||||
* - 折疊/展開動畫 - 機械爪開合意象
|
||||
* - i18n 導航標籤
|
||||
* - 當前路由高亮
|
||||
* - lucide-react 圖示 (無實體圖片依賴)
|
||||
* 5 大核心樞紐:
|
||||
* 1. 全局戰情室 (/)
|
||||
* 2. 授權中心 (/authorizations) - 含動態徽章
|
||||
* 3. 行動日誌 (/action-logs)
|
||||
* 4. 知識殿堂 (/knowledge-base)
|
||||
* 5. 系統設定 (/settings)
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { StatusOrb } from '@/components/ui/status-orb'
|
||||
import Link from 'next/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
LayoutDashboard,
|
||||
@@ -26,12 +28,10 @@ import {
|
||||
Zap,
|
||||
BookOpen,
|
||||
Settings,
|
||||
Bot,
|
||||
Eye,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Sparkles,
|
||||
} from 'lucide-react'
|
||||
import { apiClient } from '@/lib/api-client'
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
@@ -44,22 +44,23 @@ interface SidebarProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Navigation Items (using lucide-react icons)
|
||||
// =============================================================================
|
||||
|
||||
type NavItemConfig = {
|
||||
id: string
|
||||
href: string
|
||||
labelKey: string
|
||||
Icon: typeof LayoutDashboard
|
||||
badge?: boolean // 是否顯示動態徽章
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Phase 7.0: 5 大核心樞紐
|
||||
// =============================================================================
|
||||
|
||||
const NAV_ITEMS: NavItemConfig[] = [
|
||||
{ id: 'dashboard', href: '/demo', labelKey: 'dashboard', Icon: LayoutDashboard },
|
||||
{ id: 'approvals', href: '/approvals', labelKey: 'approvals', Icon: ShieldCheck },
|
||||
{ id: 'actions', href: '/action-logs', labelKey: 'actions', Icon: Zap },
|
||||
{ id: 'knowledge', href: '/knowledge', labelKey: 'knowledge', Icon: BookOpen },
|
||||
{ id: 'warroom', href: '/', labelKey: 'dashboard', Icon: LayoutDashboard },
|
||||
{ id: 'authorizations', href: '/authorizations', labelKey: 'approvals', Icon: ShieldCheck, badge: true },
|
||||
{ id: 'action-logs', href: '/action-logs', labelKey: 'actions', Icon: Zap },
|
||||
{ id: 'knowledge-base', href: '/knowledge-base', labelKey: 'knowledge', Icon: BookOpen },
|
||||
{ id: 'settings', href: '/settings', labelKey: 'settings', Icon: Settings },
|
||||
]
|
||||
|
||||
@@ -75,11 +76,31 @@ export function Sidebar({
|
||||
}: SidebarProps) {
|
||||
const t = useTranslations('nav')
|
||||
const tBrand = useTranslations('brand')
|
||||
const tClawbot = useTranslations('openclaw')
|
||||
const pathname = usePathname()
|
||||
const [pendingCount, setPendingCount] = useState(0)
|
||||
|
||||
// 載入待簽核數量 (動態徽章)
|
||||
useEffect(() => {
|
||||
const fetchPendingCount = async () => {
|
||||
try {
|
||||
const data = await apiClient.getPendingApprovals()
|
||||
setPendingCount(data.count || 0)
|
||||
} catch {
|
||||
setPendingCount(0)
|
||||
}
|
||||
}
|
||||
|
||||
fetchPendingCount()
|
||||
// 每 30 秒刷新
|
||||
const interval = setInterval(fetchPendingCount, 30000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const isActive = (href: string) => {
|
||||
const fullHref = `/${locale}${href}`
|
||||
const fullHref = `/${locale}${href === '/' ? '' : href}`
|
||||
if (href === '/') {
|
||||
return pathname === `/${locale}` || pathname === `/${locale}/`
|
||||
}
|
||||
return pathname === fullHref || pathname.startsWith(fullHref + '/')
|
||||
}
|
||||
|
||||
@@ -88,150 +109,111 @@ export function Sidebar({
|
||||
className={cn(
|
||||
'fixed left-0 top-0 h-screen z-40',
|
||||
'flex flex-col',
|
||||
// 玻璃效果
|
||||
'bg-white/70 backdrop-blur-[20px]',
|
||||
'border-r border-black/[0.06]',
|
||||
// Phase 7.0: 純白極簡
|
||||
'bg-white',
|
||||
'border-r-[0.5px] border-neutral-200',
|
||||
// 寬度動畫
|
||||
'transition-all duration-300 ease-out',
|
||||
collapsed ? 'w-16' : 'w-64',
|
||||
collapsed ? 'w-16' : 'w-56',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Logo - lucide-react + CSS Typography (第七章規範) */}
|
||||
{/* Logo 區 - 極簡化 */}
|
||||
<div className={cn(
|
||||
'flex items-center border-b border-black/[0.04]',
|
||||
'flex items-center border-b-[0.5px] border-neutral-200',
|
||||
'transition-all duration-300',
|
||||
collapsed ? 'h-20 justify-center px-3' : 'h-24 px-6'
|
||||
collapsed ? 'h-16 justify-center px-2' : 'h-16 px-4'
|
||||
)}>
|
||||
<div className={cn(
|
||||
'flex items-center gap-3',
|
||||
collapsed ? 'flex-col' : ''
|
||||
)}>
|
||||
{/* Bot Icon + 呼吸燈效果 */}
|
||||
<div className="relative">
|
||||
<div className={cn(
|
||||
'w-10 h-10 rounded-xl flex items-center justify-center',
|
||||
'bg-gradient-to-br from-claw-blue/20 to-claw-blue/10',
|
||||
'border border-claw-blue/30',
|
||||
'transition-all duration-300'
|
||||
)}>
|
||||
<Eye className="w-5 h-5 text-claw-blue" />
|
||||
</div>
|
||||
{/* Breathing indicator */}
|
||||
<div className="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-claw-blue opacity-75" />
|
||||
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-claw-blue" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 品牌字體排版 - i18n 規範 */}
|
||||
{!collapsed && (
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xl font-black tracking-widest text-nothing-black uppercase font-mono">
|
||||
{tBrand('name')}
|
||||
</span>
|
||||
<span className="text-[9px] text-nothing-gray-500 tracking-widest font-mono font-medium uppercase">
|
||||
{tBrand('version')} | {tBrand('tagline')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{collapsed ? (
|
||||
<span className="font-mono text-lg font-bold text-neutral-900 tracking-tighter">
|
||||
A
|
||||
</span>
|
||||
) : (
|
||||
<span className="font-mono text-sm font-bold text-neutral-900 tracking-widest uppercase">
|
||||
{tBrand('name')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Navigation - lucide-react Icons */}
|
||||
<nav className="flex-1 py-6 overflow-y-auto">
|
||||
<ul className="space-y-1.5 px-3">
|
||||
{/* 導航列表 - 5 大核心樞紐 */}
|
||||
<nav className="flex-1 py-4 overflow-y-auto">
|
||||
<ul className="space-y-0.5 px-2">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const active = isActive(item.href)
|
||||
return (
|
||||
<li key={item.id}>
|
||||
<a
|
||||
href={`/${locale}${item.href}`}
|
||||
<Link
|
||||
href={`/${locale}${item.href === '/' ? '' : item.href}`}
|
||||
className={cn(
|
||||
'flex items-center gap-3.5 px-3.5 py-3 rounded-xl',
|
||||
'transition-all duration-200',
|
||||
'group relative',
|
||||
'flex items-center gap-3 px-3 py-2.5 rounded-sm',
|
||||
'transition-all duration-150',
|
||||
'relative',
|
||||
// Phase 7.0 視覺規範
|
||||
active
|
||||
? 'bg-nothing-black text-white shadow-lg shadow-black/20'
|
||||
: 'text-nothing-gray-600 hover:bg-nothing-gray-100/80 hover:text-nothing-black',
|
||||
? 'font-bold text-black bg-neutral-100'
|
||||
: 'text-neutral-500 hover:bg-neutral-50 hover:text-black',
|
||||
collapsed && 'justify-center px-2'
|
||||
)}
|
||||
>
|
||||
{/* Active indicator - 左側條紋 */}
|
||||
{active && !collapsed && (
|
||||
<span className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 bg-white/30 rounded-r-full" />
|
||||
)}
|
||||
<item.Icon className={cn(
|
||||
'w-5 h-5 flex-shrink-0 transition-all duration-200',
|
||||
active ? 'opacity-100' : 'opacity-60 group-hover:opacity-100',
|
||||
'group-hover:scale-105'
|
||||
'w-[18px] h-[18px] flex-shrink-0',
|
||||
active ? 'text-black' : 'text-neutral-400'
|
||||
)} />
|
||||
|
||||
{!collapsed && (
|
||||
<span className="font-medium text-[13px] tracking-wide truncate">
|
||||
<span className="text-[13px] tracking-wide truncate">
|
||||
{t(item.labelKey)}
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
|
||||
{/* 動態徽章 - 授權中心 */}
|
||||
{item.badge && pendingCount > 0 && (
|
||||
<span className={cn(
|
||||
'flex items-center justify-center',
|
||||
'min-w-[18px] h-[18px] px-1',
|
||||
'text-[10px] font-mono font-bold',
|
||||
'bg-neutral-900 text-white rounded-sm',
|
||||
collapsed ? 'absolute top-1 right-1' : 'ml-auto'
|
||||
)}>
|
||||
{pendingCount > 99 ? '99+' : pendingCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/* OpenClaw Status - lucide-react Bot Icon */}
|
||||
{/* 底部區域 - 極簡版本號 */}
|
||||
<div className={cn(
|
||||
'border-t border-black/[0.04] p-5',
|
||||
collapsed ? 'flex justify-center' : ''
|
||||
'border-t-[0.5px] border-neutral-200 py-3',
|
||||
collapsed ? 'px-2 text-center' : 'px-4'
|
||||
)}>
|
||||
<div className={cn(
|
||||
'flex items-center',
|
||||
collapsed ? 'flex-col gap-2' : 'gap-3.5'
|
||||
)}>
|
||||
{/* Bot Icon with glow */}
|
||||
<div className="relative">
|
||||
<div className={cn(
|
||||
'w-10 h-10 rounded-xl flex items-center justify-center',
|
||||
'bg-gradient-to-br from-status-thinking/20 to-status-thinking/10',
|
||||
'border border-status-thinking/30'
|
||||
)}>
|
||||
<Bot className="w-5 h-5 text-status-thinking" />
|
||||
</div>
|
||||
{/* Sparkle effect */}
|
||||
<Sparkles className="absolute -top-1 -right-1 w-3 h-3 text-status-thinking animate-pulse" />
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-sm font-bold text-nothing-black tracking-tight">
|
||||
{tClawbot('name')}
|
||||
</span>
|
||||
<span className="text-[9px] text-status-thinking font-mono uppercase tracking-widest">
|
||||
{tClawbot('monitoring')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<span className="text-[10px] font-mono text-neutral-300 uppercase tracking-widest">
|
||||
v1.0.0
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Collapse Toggle - lucide-react Chevron */}
|
||||
{/* 折疊按鈕 */}
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className={cn(
|
||||
'absolute -right-4 top-24',
|
||||
'w-8 h-8 rounded-full',
|
||||
'bg-white border-2 border-nothing-gray-200/80',
|
||||
'shadow-md shadow-black/5',
|
||||
'absolute -right-3 top-20',
|
||||
'w-6 h-6 rounded-full',
|
||||
'bg-white border-[0.5px] border-neutral-200',
|
||||
'flex items-center justify-center',
|
||||
'hover:bg-nothing-gray-50 hover:border-nothing-gray-300',
|
||||
'hover:shadow-lg hover:shadow-black/10',
|
||||
'active:scale-95',
|
||||
'transition-all duration-200'
|
||||
'hover:bg-neutral-50',
|
||||
'transition-all duration-150'
|
||||
)}
|
||||
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
|
||||
>
|
||||
{collapsed ? (
|
||||
<ChevronRight className="w-4 h-4 text-nothing-gray-600" />
|
||||
<ChevronRight className="w-3 h-3 text-neutral-400" />
|
||||
) : (
|
||||
<ChevronLeft className="w-4 h-4 text-nothing-gray-600" />
|
||||
<ChevronLeft className="w-3 h-3 text-neutral-400" />
|
||||
)}
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
Reference in New Issue
Block a user