"use client";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import type { ComponentType, SVGProps } from "react";
import { Avatar as MuAvatar, Menu, MenuItem, MenuSeparator, Sidebar } from "@unlocalhosted/metalui";
import { logoutAction } from "@/app/actions/auth";
import type { Theme } from "@/lib/theme";
import { BRAND, LogoMark } from "./brand";
import { ChevronsUpDownIcon, CompassIcon, GearIcon, HomeIcon, KeyIcon, MenuIcon, PersonIcon, PlusIcon, RepoIcon, ShieldLockIcon, SignOutIcon } from "./icons";
import { ThemeToggle } from "./theme-toggle";
import { RepoAvatar } from "./ui";
type Item = { href: string; label: string; icon: ComponentType<SVGProps<SVGSVGElement>>; match: (p: string) => boolean };
export type SidebarRepo = { owner: string; name: string; imageVersion: number | null };
export type SidebarViewer = { username: string; name: string };
function navItems(username: string): Item[] {
return [
{ href: "/", label: "Overview", icon: HomeIcon, match: (p) => p === "/" },
{ href: `/${username}`, label: "Repositories", icon: RepoIcon, match: (p) => p === `/${username}` },
{ href: "/explore", label: "Explore", icon: CompassIcon, match: (p) => p.startsWith("/explore") },
{ href: "/settings/profile", label: "Settings", icon: GearIcon, match: (p) => p.startsWith("/settings") },
];
}
/** The account's rows: who you are, then where your account lives, then Sign Out (last, on its own). */
function AccountMenuItems({ viewer }: { viewer: SidebarViewer }) {
const router = useRouter();
return (
<>
<MenuItem icon={<PersonIcon />} onSelect={() => router.push(`/${viewer.username}`)}>
Your Profile
</MenuItem>
<MenuItem icon={<GearIcon />} onSelect={() => router.push("/settings/profile")}>
Settings
</MenuItem>
<MenuItem icon={<KeyIcon />} onSelect={() => router.push("/settings/keys")}>
SSH Keys
</MenuItem>
<MenuItem icon={<ShieldLockIcon />} onSelect={() => router.push("/settings/tokens")}>
API Tokens
</MenuItem>
<MenuSeparator />
<MenuItem icon={<SignOutIcon />} onSelect={() => void logoutAction()}>
Sign Out
</MenuItem>
</>
);
}
/** Left rail for signed-in users: MetalUI's Sidebar with the account menu, the places, and repository shortcuts. */
export function AppSidebar({ viewer, repos, theme }: { viewer: SidebarViewer; repos: SidebarRepo[]; theme: Theme }) {
const pathname = usePathname();
return (
<aside className="sticky top-0 hidden h-screen shrink-0 lg:block">
<Sidebar aria-label="Navigation">
<Sidebar.Header>
<Menu
heading={`SIGNED IN AS @${viewer.username.toUpperCase()}`}
align="start"
trigger={
<button
type="button"
aria-label="Open account menu"
className="mu-icon-trigger flex h-11 w-full items-center gap-2.5 rounded-[12px] px-2 text-left outline-none transition-colors hover:recipe-row-list-hover focus-visible:focus-ring data-popup-open:recipe-row-list-hover"
>
<MuAvatar name={viewer.name || viewer.username} size="small" />
<span className="min-w-0 flex-1">
<span className="block truncate type-title text-ink">{viewer.name || viewer.username}</span>
<span className="block truncate type-meta text-ink3">@{viewer.username}</span>
</span>
<ChevronsUpDownIcon width={14} height={14} className="text-ink3" />
</button>
}
>
<AccountMenuItems viewer={viewer} />
</Menu>
</Sidebar.Header>
<Sidebar.Section title="Places">
{navItems(viewer.username).map((item) => (
<Sidebar.Item key={item.href} icon={<item.icon />} active={item.match(pathname)} render={<Link href={item.href} />}>
{item.label}
</Sidebar.Item>
))}
</Sidebar.Section>
<Sidebar.Section title={`Repositories · ${repos.length}`}>
{repos.slice(0, 12).map((r) => {
const base = `/${r.owner}/${r.name}`;
const active = pathname === base || pathname.startsWith(`${base}/`);
return (
<Sidebar.Item key={base} icon={<RepoAvatar repo={r} size={16} />} active={active} render={<Link href={base} />}>
{r.owner !== viewer.username ? `${r.owner}/${r.name}` : r.name}
</Sidebar.Item>
);
})}
<Sidebar.Item icon={<PlusIcon />} active={pathname === "/new"} render={<Link href="/new" />}>
New Repository
</Sidebar.Item>
</Sidebar.Section>
<Sidebar.Footer>
<div className="flex flex-col gap-3 px-1.5 pb-1">
<ThemeToggle initial={theme} />
<Link href="/" aria-label="Home" className="flex items-center gap-2 text-ink3 transition-colors hover:text-ink">
<LogoMark size={16} />
<span className="type-label engraved">{BRAND.name}</span>
</Link>
</div>
</Sidebar.Footer>
</Sidebar>
</aside>
);
}
/** The same places as a menu, for small screens. */
export function MobileNav({ viewer }: { viewer: SidebarViewer }) {
const router = useRouter();
return (
<Menu
heading="GO TO"
align="start"
trigger={
<button
type="button"
aria-label="Open navigation"
className="mu-icon-trigger material-cap flex size-8 items-center justify-center rounded-full text-ink2 outline-none transition-colors hover:text-ink focus-visible:focus-ring"
>
<MenuIcon width={15} height={15} />
</button>
}
>
{navItems(viewer.username).map((item) => (
<MenuItem key={item.href} icon={<item.icon />} onSelect={() => router.push(item.href)}>
{item.label}
</MenuItem>
))}
<MenuSeparator />
<AccountMenuItems viewer={viewer} />
</Menu>
);
}