177 lines
6.0 KiB
TypeScript
Vendored
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>
|
|
)
|
|
}
|