Files
new-api/web/daisy/src/components/layout/Sidebar.tsx
T
admin e83ec743c8
Docker Build / Build and Push Docker Image (push) Failing after 1m35s
feat: add DaisyUI frontend theme and document management system
2026-06-13 01:36:06 +08:00

177 lines
6.0 KiB
TypeScript
Vendored

import { useLocation } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import {
LayoutDashboard,
Key,
Wallet,
CreditCard,
ScrollText,
Image,
ListTodo,
User,
Terminal,
BookOpen,
Settings,
Server,
Users,
Gift,
Cpu,
Truck,
Layers,
} from 'lucide-react'
import { usePermission } from '@/hooks/usePermission'
import { cn } from '@/lib/utils'
import { useUIStore } from '@/stores/ui'
interface NavItem {
label: string
path: string
icon: React.ReactNode
minRole?: number
}
export default function Sidebar() {
const { t } = useTranslation()
const location = useLocation()
const { isUser, isAdmin, isRoot } = usePermission()
const { setSidebarOpen, sidebarCollapsed } = useUIStore()
const userNavItems: NavItem[] = [
{ label: t('nav.dashboard'), path: '/dashboard', icon: <LayoutDashboard size={18} /> },
{ label: t('nav.tokens'), path: '/tokens', icon: <Key size={18} /> },
{ label: t('nav.wallet'), path: '/wallet', icon: <Wallet size={18} /> },
{ label: t('nav.subscriptions'), path: '/subscriptions', icon: <CreditCard size={18} /> },
{ label: t('nav.logs'), path: '/logs', icon: <ScrollText size={18} /> },
{ label: t('nav.midjourney'), path: '/logs/midjourney', icon: <Image size={18} /> },
{ label: t('nav.tasks'), path: '/logs/tasks', icon: <ListTodo size={18} /> },
{ label: t('nav.profile'), path: '/profile', icon: <User size={18} /> },
{ label: t('nav.playground'), path: '/playground', icon: <Terminal size={18} /> },
{ label: t('nav.docs'), path: '/docs', icon: <BookOpen size={18} /> },
]
const adminNavItems: NavItem[] = [
{ label: t('nav.channels'), path: '/admin/channels', icon: <Server size={18} />, minRole: 10 },
{ label: t('nav.users'), path: '/admin/users', icon: <Users size={18} />, minRole: 10 },
{ label: t('nav.redemptions'), path: '/admin/redemptions', icon: <Gift size={18} />, minRole: 10 },
{ label: t('nav.models'), path: '/admin/models', icon: <Cpu size={18} />, minRole: 10 },
{ label: t('nav.vendors'), path: '/admin/vendors', icon: <Truck size={18} />, minRole: 10 },
{ label: t('nav.deployments'), path: '/admin/deployments', icon: <Layers size={18} />, minRole: 10 },
{ label: t('nav.subscriptions'), path: '/admin/subscriptions', icon: <CreditCard size={18} />, minRole: 10 },
]
const rootNavItems: NavItem[] = [
{ label: t('nav.settings'), path: '/settings/site', icon: <Settings size={18} />, minRole: 100 },
]
const isActive = (path: string) => {
if (path === '/logs') return location.pathname === '/logs'
return location.pathname.startsWith(path)
}
const handleNavClick = () => {
setSidebarOpen(false)
}
const filterByRole = (items: NavItem[]) =>
items.filter((item) => {
if (!item.minRole) return true
if (item.minRole <= 1) return isUser
if (item.minRole <= 10) return isAdmin
return isRoot
})
return (
<aside
className={cn(
'bg-base-100 h-full flex flex-col',
sidebarCollapsed ? 'w-16' : 'w-64'
)}
>
<div className="p-4 border-b border-base-300">
<h1 className="text-xl font-bold text-primary">ModelsToken</h1>
</div>
<nav className="flex-1 overflow-y-auto">
<ul className="menu menu-md p-2 gap-1">
{filterByRole(userNavItems).map((item) => (
<li key={item.path}>
<a
href={item.path}
onClick={(e) => {
e.preventDefault()
handleNavClick()
window.history.pushState({}, '', item.path)
window.dispatchEvent(new PopStateEvent('popstate'))
}}
className={cn(
'flex items-center gap-3 rounded-lg',
isActive(item.path) && 'active'
)}
>
{item.icon}
{!sidebarCollapsed && <span>{item.label}</span>}
</a>
</li>
))}
{filterByRole(adminNavItems).length > 0 && (
<>
<li className="menu-title mt-2">
{!sidebarCollapsed && <span>{t('nav.channels')}</span>}
</li>
{filterByRole(adminNavItems).map((item) => (
<li key={item.path}>
<a
href={item.path}
onClick={(e) => {
e.preventDefault()
handleNavClick()
window.history.pushState({}, '', item.path)
window.dispatchEvent(new PopStateEvent('popstate'))
}}
className={cn(
'flex items-center gap-3 rounded-lg',
isActive(item.path) && 'active'
)}
>
{item.icon}
{!sidebarCollapsed && <span>{item.label}</span>}
</a>
</li>
))}
</>
)}
{isRoot && (
<>
<li className="menu-title mt-2">
{!sidebarCollapsed && <span>{t('nav.settings')}</span>}
</li>
{filterByRole(rootNavItems).map((item) => (
<li key={item.path}>
<a
href={item.path}
onClick={(e) => {
e.preventDefault()
handleNavClick()
window.history.pushState({}, '', item.path)
window.dispatchEvent(new PopStateEvent('popstate'))
}}
className={cn(
'flex items-center gap-3 rounded-lg',
isActive(item.path) && 'active'
)}
>
{item.icon}
{!sidebarCollapsed && <span>{item.label}</span>}
</a>
</li>
))}
</>
)}
</ul>
</nav>
</aside>
)
}