2025-04-25 16:24:04 +08:00
|
|
|
'use client'
|
2025-05-06 15:49:02 +08:00
|
|
|
import {ReactNode, useState} from 'react'
|
2025-04-25 16:24:04 +08:00
|
|
|
import {merge} from '@/lib/utils'
|
|
|
|
|
import {useLayoutStore} from '@/components/providers/StoreProvider'
|
|
|
|
|
import Link from 'next/link'
|
|
|
|
|
import Image from 'next/image'
|
2025-04-26 17:20:21 +08:00
|
|
|
import logoAvatar from '../_assets/logo-avatar.svg'
|
|
|
|
|
import logoText from '../_assets/logo-text.svg'
|
2025-05-06 15:49:02 +08:00
|
|
|
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from '@/components/ui/tooltip'
|
2025-06-05 16:41:30 +08:00
|
|
|
import { UserRound } from 'lucide-react'
|
|
|
|
|
import { UserRoundPen } from 'lucide-react'
|
|
|
|
|
import { IdCard } from 'lucide-react'
|
|
|
|
|
import { LockKeyhole } from 'lucide-react'
|
|
|
|
|
import { Wallet } from 'lucide-react'
|
|
|
|
|
import { ShoppingCart } from 'lucide-react'
|
|
|
|
|
import { Package } from 'lucide-react'
|
|
|
|
|
import { HardDriveUpload } from 'lucide-react'
|
|
|
|
|
import { Eye } from 'lucide-react'
|
|
|
|
|
import { Archive } from 'lucide-react'
|
|
|
|
|
import { ArchiveRestore } from 'lucide-react'
|
|
|
|
|
|
2025-04-25 16:24:04 +08:00
|
|
|
|
|
|
|
|
export type NavbarProps = {}
|
|
|
|
|
|
2025-06-05 16:41:30 +08:00
|
|
|
|
2025-04-25 16:24:04 +08:00
|
|
|
export default function Navbar(props: NavbarProps) {
|
|
|
|
|
|
|
|
|
|
const navbar = useLayoutStore(store => store.navbar)
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<nav data-expand={navbar} className={merge(
|
2025-04-26 17:20:21 +08:00
|
|
|
`transition-[flex-basis] duration-300 ease-in-out`,
|
|
|
|
|
`flex-none`,
|
2025-04-25 16:24:04 +08:00
|
|
|
`flex flex-col overflow-hidden group`,
|
2025-04-26 14:18:08 +08:00
|
|
|
`data-[expand=true]:basis-52 data-[expand=false]:basis-16`,
|
2025-04-26 17:20:21 +08:00
|
|
|
` `,
|
2025-04-25 16:24:04 +08:00
|
|
|
)}>
|
|
|
|
|
{/* logo */}
|
2025-04-26 17:20:21 +08:00
|
|
|
<Link href={'/'} className={merge(
|
|
|
|
|
`flex-none h-[64px] flex items-center justify-center`,
|
|
|
|
|
)}>
|
|
|
|
|
<Image src={logoAvatar} alt={`logo`} className={`w-10 h-10 object-contain`}/>
|
|
|
|
|
<Image src={logoText} alt={`logo`} className={merge(
|
|
|
|
|
`h-10 translate-1 object-cover object-left`,
|
|
|
|
|
`transition-[opacity,width] duration-[200ms,300ms] ease-in-out`,
|
|
|
|
|
`group-data-[expand=true]:delay-[100ms,0ms]`,
|
|
|
|
|
`group-data-[expand=true]:opacity-100 group-data-[expand=false]:opacity-0`,
|
|
|
|
|
`group-data-[expand=true]:w-[85px] group-data-[expand=false]:w-0`,
|
|
|
|
|
)}/>
|
|
|
|
|
</Link>
|
2025-04-25 16:24:04 +08:00
|
|
|
|
|
|
|
|
{/* routes */}
|
|
|
|
|
<section className={merge(
|
2025-04-26 17:20:21 +08:00
|
|
|
`transition-[padding] duration-300 ease-in-out`,
|
2025-04-26 14:18:08 +08:00
|
|
|
`flex-auto overflow-auto`,
|
2025-04-26 17:20:21 +08:00
|
|
|
`group-data-[expand=true]:px-4 group-data-[expand=false]:px-3`,
|
2025-04-25 16:24:04 +08:00
|
|
|
)}>
|
2025-05-06 15:49:02 +08:00
|
|
|
<TooltipProvider>
|
2025-06-05 16:41:30 +08:00
|
|
|
<NavItem href={'/admin'} icon={<UserRound size={20}/>} label={`账户总览`} expand={navbar}/>
|
2025-05-06 15:49:02 +08:00
|
|
|
<NavTitle label={`个人信息`}/>
|
2025-06-05 16:41:30 +08:00
|
|
|
<NavItem href={`/admin/profile`} icon={<UserRoundPen size={20}/>} label={`个人中心`} expand={navbar}/>
|
|
|
|
|
<NavItem href={`/admin/identify`} icon={<IdCard size={20}/>} label={`实名认证`} expand={navbar}/>
|
|
|
|
|
<NavItem href={`/admin/whitelist`} icon={<LockKeyhole size={20}/>} label={`白名单`} expand={navbar}/>
|
|
|
|
|
<NavItem href={`/admin/bills`} icon={<Wallet size={20}/>} label={`我的账单`} expand={navbar}/>
|
2025-05-06 15:49:02 +08:00
|
|
|
<NavTitle label={`套餐管理`}/>
|
2025-06-05 16:41:30 +08:00
|
|
|
<NavItem href={`/admin/purchase`} icon={<ShoppingCart size={20}/>} label={`购买套餐`} expand={navbar}/>
|
|
|
|
|
<NavItem href={`/admin/resources`} icon={<Package size={20}/>} label={`套餐管理`} expand={navbar}/>
|
2025-05-06 15:49:02 +08:00
|
|
|
<NavTitle label={`IP 管理`}/>
|
2025-06-05 16:41:30 +08:00
|
|
|
<NavItem href={`/admin/extract`} icon={<HardDriveUpload size={20}/>} label={`提取 IP`} expand={navbar}/>
|
|
|
|
|
<NavItem href={`/admin/channels`} icon={<Eye size={20}/>} label={`IP 管理`} expand={navbar}/>
|
|
|
|
|
<NavItem href={`/admin`} icon={<Archive size={20}/>} label={`提取记录`} expand={navbar}/>
|
|
|
|
|
<NavItem href={`/admin`} icon={<ArchiveRestore size={20}/>} label={`使用记录`} expand={navbar}/>
|
2025-05-06 15:49:02 +08:00
|
|
|
</TooltipProvider>
|
2025-04-25 16:24:04 +08:00
|
|
|
</section>
|
|
|
|
|
</nav>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function NavTitle(props: {
|
|
|
|
|
label: string
|
|
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<p className={merge(
|
2025-04-26 17:20:21 +08:00
|
|
|
`transition-[height] duration-300 ease-in-out`,
|
2025-04-25 16:24:04 +08:00
|
|
|
`text-sm text-gray-500 whitespace-nowrap flex items-center relative`,
|
|
|
|
|
`group-data-[expand=true]:h-9`,
|
|
|
|
|
`group-data-[expand=false]:h-4`,
|
|
|
|
|
)}>
|
|
|
|
|
<span className={merge(
|
2025-04-26 17:20:21 +08:00
|
|
|
`transition-[opacity] duration-200 ease-in-out absolute mx-4`,
|
|
|
|
|
`group-data-[expand=true]:delay-100 group-data-[expand=true]:opacity-100 group-data-[expand=false]:opacity-0`,
|
2025-04-25 16:24:04 +08:00
|
|
|
)}>{props.label}</span>
|
2025-04-26 14:18:08 +08:00
|
|
|
<span className={merge(
|
2025-04-26 17:20:21 +08:00
|
|
|
`transition-[opacity] duration-200 ease-in-out absolute w-full border-b block`,
|
|
|
|
|
`group-data-[expand=false]:delay-100 group-data-[expand=false]:opacity-100 group-data-[expand=true]:opacity-0`,
|
2025-04-26 14:18:08 +08:00
|
|
|
)}></span>
|
2025-04-25 16:24:04 +08:00
|
|
|
</p>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function NavItem(props: {
|
|
|
|
|
href: string
|
|
|
|
|
icon?: ReactNode
|
|
|
|
|
label: string
|
2025-05-06 15:49:02 +08:00
|
|
|
expand?: boolean
|
2025-04-25 16:24:04 +08:00
|
|
|
}) {
|
2025-05-06 15:49:02 +08:00
|
|
|
|
|
|
|
|
const [open, setOpen] = useState(false)
|
|
|
|
|
|
|
|
|
|
const handleOpenChange = (open: boolean) => {
|
|
|
|
|
if (!props.expand) {
|
|
|
|
|
setOpen(open)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-25 16:24:04 +08:00
|
|
|
return (
|
2025-05-06 15:49:02 +08:00
|
|
|
<Tooltip open={open} onOpenChange={handleOpenChange}>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Link className={merge(
|
|
|
|
|
`transition-[padding] duration-300 ease-in-out`,
|
|
|
|
|
`flex items-center rounded-md gap-2 whitespace-nowrap`,
|
|
|
|
|
`hover:bg-gray-100`,
|
|
|
|
|
`group-data-[expand=true]:px-4`,
|
|
|
|
|
)} href={props.href}>
|
|
|
|
|
<span className={`flex-none w-10 h-10 flex items-center justify-center`}>{props.icon}</span>
|
|
|
|
|
<span className={merge(
|
|
|
|
|
`flex-auto`,
|
|
|
|
|
`transition-[width,opacity] duration-300 ease-in-out`,
|
|
|
|
|
`group-data-[expand=true]:w-auto group-data-[expand=true]:opacity-100`,
|
|
|
|
|
`group-data-[expand=false]:w-0 group-data-[expand=false]:opacity-0`,
|
|
|
|
|
)}>{props.label}</span>
|
|
|
|
|
</Link>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent side={`right`} sideOffset={16}>
|
|
|
|
|
<p>{props.label}</p>
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
2025-04-25 16:24:04 +08:00
|
|
|
)
|
|
|
|
|
}
|