mosaicmap/app/app-sidebar.tsx
2025-08-18 18:47:30 +08:00

257 lines
9.0 KiB
TypeScript

"use client"
import * as React from "react"
import { Book, Command, Home, LucideIcon, Plus, User, Settings, Crown, LogOut } from "lucide-react"
import { motion } from "framer-motion"
import Link from "next/link"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
} from '@/components/ui/sidebar'
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { useMapLocation, type LocationKey } from "@/hooks/use-map-location"
import { ThemeToggle } from "@/components/theme-toggle"
import { useMapZoom } from "@/hooks/use-map-zoom"
import { useUser } from "./user-context"
import { Separator } from "@/components/ui/separator"
import { cn } from "@/lib/utils"
import { useRouter } from "next/navigation"
interface MainNavItemProps {
icon: LucideIcon
label: string
onClick?: () => void
className?: string
}
const MainNavItem = React.forwardRef<HTMLButtonElement, MainNavItemProps>(
({ icon: Icon, label, onClick, className }, ref) => {
return (
<motion.button
ref={ref}
whileHover={{ scale: 1.1, x: -2 }}
whileTap={{ scale: 0.95 }}
onClick={onClick}
className={cn(
"relative group p-2 rounded-md",
"hover:bg-secondary transition-colors",
className
)}
>
<Icon className="w-4 h-4 text-foreground" />
<span className={cn(
"absolute left-full top-1/2 -translate-y-1/2 ml-2",
"px-2 py-1 rounded text-xs",
"bg-popover text-popover-foreground",
"opacity-0 group-hover:opacity-100",
"transition-opacity whitespace-nowrap pointer-events-none",
"z-[9999]"
)}>
{label}
</span>
</motion.button>
)
}
)
MainNavItem.displayName = "MainNavItem"
interface MainNavProps {
className?: string
items: {
icon: LucideIcon
label: string
onClick?: () => void
}[]
}
const MainNav = React.forwardRef<HTMLDivElement, MainNavProps>(
({ items, className }, ref) => {
return (
<div ref={ref} className={cn("flex flex-col items-center gap-2 px-2", className)}>
{items.map((item) => (
<MainNavItem key={item.label} {...item} />
))}
</div>
)
}
)
MainNav.displayName = "MainNav"
// User avatar component
function UserAvatar() {
const { user } = useUser()
if (user) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="cursor-pointer"
>
<Avatar className="w-8 h-8">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className="bg-sidebar-accent text-sidebar-accent-foreground">
{user.name?.charAt(0)?.toUpperCase() || "U"}
</AvatarFallback>
</Avatar>
</motion.div>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end">
<DropdownMenuLabel>
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">{user.name}</p>
<p className="text-xs leading-none text-muted-foreground">{user.email}</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<Link href="/me" className="flex items-center">
<Settings className="mr-2 h-4 w-4" />
<span>Profile</span>
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</Link>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
{/* <DropdownMenuGroup> */}
{/* <DropdownMenuItem asChild>
<Link href="/me/upgrade" className="flex items-center">
<Crown className="mr-2 h-4 w-4" />
<span>Upgrade to Pro</span>
<DropdownMenuShortcut>⌘U</DropdownMenuShortcut>
</Link>
</DropdownMenuItem> */}
{/* <DropdownMenuItem asChild>
<Link href="/me/billing" className="flex items-center">
<Crown className="mr-2 h-4 w-4" />
<span>Billing</span>
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
</Link>
</DropdownMenuItem> */}
{/* </DropdownMenuGroup> */}
{/* <DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link href="/help" className="flex items-center">
<Settings className="mr-2 h-4 w-4" />
<span>Help Center</span>
</Link>
</DropdownMenuItem>
<DropdownMenuItem disabled>
API Documentation
</DropdownMenuItem>
<DropdownMenuSeparator /> */}
<DropdownMenuItem className="flex items-center text-red-600 focus:text-red-600">
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
return (
<Link href="/login">
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="w-8 h-8 rounded-full bg-sidebar-accent flex items-center justify-center cursor-pointer"
>
<User className="w-4 h-4 text-sidebar-accent-foreground" />
</motion.div>
</Link>
)
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const router = useRouter()
const mainNavItems = [
// {
// icon: Home,
// label: "Home",
// onClick: () => console.log("Navigate to home")
// },
{
icon: Book,
label: "Blogs",
onClick: () => router.push("/blog")
},
// {
// icon: Plus,
// label: "New",
// onClick: () => console.log("Create new project")
// }
]
return (
<div className="bg-sidebar text-sidebar-foreground w-[48px] h-full flex flex-col items-center p-4 border-r border-sidebar-border">
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<Command className="size-4" />
</div>
<Separator className="w-full my-2" />
<MainNav items={mainNavItems} />
{/* User avatar - fixed at bottom */}
<div className="mt-auto">
<UserAvatar />
</div>
</div>
)
}
export function NavSecondary({
items,
...props
}: {
items: {
title: string
url: string
icon: LucideIcon
}[]
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
return (
<SidebarGroup {...props}>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild size="sm">
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}