mirror of
https://github.com/ershisan99/www.git
synced 2025-12-17 05:19:23 +00:00
304 lines
10 KiB
TypeScript
304 lines
10 KiB
TypeScript
'use client'
|
|
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu'
|
|
import { ThemeToggle } from 'fumadocs-ui/components/layout/theme-toggle'
|
|
import type { HomeLayoutProps } from 'fumadocs-ui/layouts/home'
|
|
import type { LinkItemType } from 'fumadocs-ui/layouts/links'
|
|
import { replaceOrDefault } from 'fumadocs-ui/layouts/shared'
|
|
import { LogIn, LogOut, Settings, Shield, Tv, User } from 'lucide-react'
|
|
import { signIn, signOut, useSession } from 'next-auth/react'
|
|
import Link from 'next/link'
|
|
import { Fragment } from 'react'
|
|
import { Menu, MenuContent, MenuLinkItem, MenuTrigger } from './home/menu'
|
|
import {
|
|
NavbarLink,
|
|
NavbarMenu,
|
|
NavbarMenuContent,
|
|
NavbarMenuLink,
|
|
NavbarMenuTrigger,
|
|
} from './home/navbar'
|
|
import { Navbar } from './home/navbar'
|
|
import { LargeSearchToggle, SearchToggle } from './search-toggle'
|
|
|
|
export function Header({
|
|
nav: { enableSearch = true, ...nav } = {},
|
|
finalLinks,
|
|
themeSwitch,
|
|
}: HomeLayoutProps & {
|
|
finalLinks: LinkItemType[]
|
|
}) {
|
|
const { data: session, status } = useSession()
|
|
const isAuthenticated = status === 'authenticated'
|
|
const isAdmin = isAuthenticated && session?.user?.role === 'admin'
|
|
const navItems = finalLinks.filter((item) =>
|
|
['nav', 'all'].includes(item.on ?? 'all')
|
|
)
|
|
const menuItems = finalLinks.filter((item) =>
|
|
['menu', 'all'].includes(item.on ?? 'all')
|
|
)
|
|
|
|
return (
|
|
<Navbar>
|
|
<Link
|
|
href={nav.url ?? '/'}
|
|
className='inline-flex items-center gap-2.5 font-semibold'
|
|
>
|
|
{nav.title}
|
|
</Link>
|
|
{nav.children}
|
|
<ul className='flex flex-row items-center gap-2 px-6 max-sm:hidden'>
|
|
{navItems
|
|
.filter((item) => !isSecondary(item))
|
|
.map((item, i) => (
|
|
<NavbarLinkItem key={i} item={item} className='text-sm' />
|
|
))}
|
|
{isAdmin && (
|
|
<NavbarMenu>
|
|
<NavbarMenuTrigger className='text-sm'>
|
|
<div className='flex items-center gap-1'>
|
|
<Shield className='h-4 w-4' />
|
|
<span>Admin</span>
|
|
</div>
|
|
</NavbarMenuTrigger>
|
|
<NavbarMenuContent>
|
|
<NavbarMenuLink href='/admin/logs'>
|
|
<p className='-mb-1 font-medium text-sm'>Logs</p>
|
|
<p className='text-[13px] text-fd-muted-foreground'>
|
|
View and manage logs
|
|
</p>
|
|
</NavbarMenuLink>
|
|
<NavbarMenuLink href='/admin/releases'>
|
|
<p className='-mb-1 font-medium text-sm'>Releases</p>
|
|
<p className='text-[13px] text-fd-muted-foreground'>
|
|
Manage releases
|
|
</p>
|
|
</NavbarMenuLink>
|
|
<NavbarMenuLink href='/admin/stream/obs-control-panel'>
|
|
<p className='-mb-1 font-medium text-sm'>OBS Control Panel</p>
|
|
<p className='text-[13px] text-fd-muted-foreground'>
|
|
Stream widget controls
|
|
</p>
|
|
</NavbarMenuLink>
|
|
</NavbarMenuContent>
|
|
</NavbarMenu>
|
|
)}
|
|
</ul>
|
|
<div className='flex flex-1 flex-row items-center justify-end gap-1.5'>
|
|
{enableSearch ? (
|
|
<>
|
|
<SearchToggle className='lg:hidden' hideIfDisabled />
|
|
<LargeSearchToggle
|
|
className='w-full max-w-[240px] max-lg:hidden'
|
|
hideIfDisabled
|
|
/>
|
|
</>
|
|
) : null}
|
|
{replaceOrDefault(
|
|
themeSwitch,
|
|
<ThemeToggle className='max-lg:hidden' mode={themeSwitch?.mode} />
|
|
)}
|
|
|
|
{/* Sign In Button or User Menu */}
|
|
{isAuthenticated && session?.user ? (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant='ghost' className='relative h-9 w-9 rounded-full'>
|
|
<Avatar className='h-9 w-9'>
|
|
<AvatarImage
|
|
src={session.user.image ?? ''}
|
|
alt={session.user.name ?? 'User'}
|
|
/>
|
|
<AvatarFallback className='bg-violet-50 text-violet-600 dark:bg-violet-900/50 dark:text-violet-300'>
|
|
{session.user.name?.slice(0, 2).toUpperCase() ?? 'U'}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent className='w-56' align='end' forceMount>
|
|
<div className='flex items-center justify-start gap-2 p-2'>
|
|
<div className='flex flex-col space-y-1 leading-none'>
|
|
<p className='font-medium'>{session.user.name}</p>
|
|
</div>
|
|
</div>
|
|
<DropdownMenuItem asChild>
|
|
<Link
|
|
href={`/players/${session.user.discord_id}`}
|
|
className='flex w-full items-center'
|
|
>
|
|
<User className='mr-2 h-4 w-4' />
|
|
<span>Profile</span>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem asChild>
|
|
<Link
|
|
href='/profile/settings'
|
|
className='flex w-full items-center'
|
|
>
|
|
<Settings className='mr-2 h-4 w-4' />
|
|
<span>Settings</span>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem asChild>
|
|
<Link
|
|
href={`/stream-card/${session.user.discord_id}`}
|
|
className='flex w-full items-center'
|
|
target='_blank'
|
|
>
|
|
<Tv className='mr-2 h-4 w-4' />
|
|
<span>Stream Widget</span>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => signOut()}>
|
|
<LogOut className='mr-2 h-4 w-4' />
|
|
<span>Sign out</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
) : (
|
|
<Button
|
|
variant='outline'
|
|
size='sm'
|
|
className='text-gray-700 hover:bg-violet-50 hover:text-violet-600 dark:text-zinc-300 dark:hover:bg-zinc-800 dark:hover:text-violet-400'
|
|
onClick={() => signIn('discord')}
|
|
>
|
|
<LogIn className='mr-2 h-4 w-4' />
|
|
Sign In
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<ul className='flex flex-row items-center'>
|
|
{navItems.filter(isSecondary).map((item, i) => (
|
|
<NavbarLinkItem
|
|
key={i}
|
|
item={item}
|
|
className='-me-1.5 max-lg:hidden'
|
|
/>
|
|
))}
|
|
<Menu className='lg:hidden'>
|
|
<MenuTrigger
|
|
aria-label='Toggle Menu'
|
|
enableHover={nav.enableHoverToOpen}
|
|
>
|
|
{/*<ChevronDown className='size-3 transition-transform duration-300 group-data-[state=open]:rotate-180' />*/}
|
|
</MenuTrigger>
|
|
<MenuContent className='sm:flex-row sm:items-center sm:justify-end'>
|
|
{menuItems
|
|
.filter((item) => !isSecondary(item))
|
|
.map((item, i) => (
|
|
<MenuLinkItem key={i} item={item} className='sm:hidden' />
|
|
))}
|
|
{isAdmin && (
|
|
<div className='sm:hidden'>
|
|
<div className='flex items-center gap-1 px-3 py-2 font-medium'>
|
|
<Shield className='h-4 w-4' />
|
|
<span>Admin</span>
|
|
</div>
|
|
<div className='ml-4 flex flex-col gap-1'>
|
|
<Link href='/admin/logs' className='px-3 py-1 text-sm'>
|
|
Logs
|
|
</Link>
|
|
<Link href='/admin/releases' className='px-3 py-1 text-sm'>
|
|
Releases
|
|
</Link>
|
|
<Link
|
|
href='/admin/stream/obs-control-panel'
|
|
className='px-3 py-1 text-sm'
|
|
>
|
|
OBS Control Panel
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className='-ms-1.5 flex flex-row items-center gap-1.5 max-sm:mt-2'>
|
|
{menuItems.filter(isSecondary).map((item, i) => (
|
|
<MenuLinkItem key={i} item={item} className='-me-1.5' />
|
|
))}
|
|
<div role='separator' className='flex-1' />
|
|
|
|
{replaceOrDefault(
|
|
themeSwitch,
|
|
<ThemeToggle mode={themeSwitch?.mode} />
|
|
)}
|
|
</div>
|
|
</MenuContent>
|
|
</Menu>
|
|
</ul>
|
|
</Navbar>
|
|
)
|
|
}
|
|
|
|
function NavbarLinkItem({
|
|
item,
|
|
...props
|
|
}: {
|
|
item: LinkItemType
|
|
className?: string
|
|
}) {
|
|
if (item.type === 'custom') return <div {...props}>{item.children}</div>
|
|
|
|
if (item.type === 'menu') {
|
|
const children = item.items.map((child, j) => {
|
|
if (child.type === 'custom')
|
|
return <Fragment key={j}>{child.children}</Fragment>
|
|
|
|
const {
|
|
banner = child.icon ? (
|
|
<div className='w-fit rounded-md border bg-fd-muted p-1 [&_svg]:size-4'>
|
|
{child.icon}
|
|
</div>
|
|
) : null,
|
|
...rest
|
|
} = child.menu ?? {}
|
|
|
|
return (
|
|
<NavbarMenuLink key={j} href={child.url} {...rest}>
|
|
{rest.children ?? (
|
|
<>
|
|
{banner}
|
|
<p className='-mb-1 font-medium text-sm'>{child.text}</p>
|
|
{child.description ? (
|
|
<p className='text-[13px] text-fd-muted-foreground'>
|
|
{child.description}
|
|
</p>
|
|
) : null}
|
|
</>
|
|
)}
|
|
</NavbarMenuLink>
|
|
)
|
|
})
|
|
|
|
return (
|
|
<NavbarMenu>
|
|
<NavbarMenuTrigger {...props}>
|
|
{item.url ? <Link href={item.url}>{item.text}</Link> : item.text}
|
|
</NavbarMenuTrigger>
|
|
<NavbarMenuContent>{children}</NavbarMenuContent>
|
|
</NavbarMenu>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<NavbarLink
|
|
{...props}
|
|
item={item}
|
|
variant={item.type}
|
|
aria-label={item.type === 'icon' ? item.label : undefined}
|
|
>
|
|
{item.type === 'icon' ? item.icon : item.text}
|
|
</NavbarLink>
|
|
)
|
|
}
|
|
|
|
function isSecondary(item: LinkItemType): boolean {
|
|
return (
|
|
('secondary' in item && item.secondary === true) || item.type === 'icon'
|
|
)
|
|
}
|