Files
GibSend/apps/web/src/components/AppSideBar.tsx
2025-05-07 23:05:46 +10:00

352 lines
9.8 KiB
TypeScript

"use client";
import {
BookUser,
Calendar,
Code,
Cog,
Globe,
Home,
Inbox,
LayoutDashboard,
LayoutTemplate,
LogOut,
Mail,
Search,
Server,
Settings,
Volume2,
BookOpenText,
ChartColumnBig,
ChartArea,
BellIcon,
CreditCardIcon,
LogOutIcon,
MoreVerticalIcon,
UserCircleIcon,
UsersIcon,
GaugeIcon,
} from "lucide-react";
import { signOut } from "next-auth/react";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@unsend/ui/src/sidebar";
import Link from "next/link";
import { MiniThemeSwitcher, ThemeSwitcher } from "./theme/ThemeSwitcher";
import { useSession } from "next-auth/react";
import { isSelfHosted } from "~/utils/common";
import { usePathname } from "next/navigation";
import { Badge } from "@unsend/ui/src/badge";
import { Avatar, AvatarFallback, AvatarImage } from "@unsend/ui/src/avatar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@unsend/ui/src/dropdown-menu";
// General items
const generalItems = [
{
title: "Analytics",
url: "/dashboard",
icon: ChartColumnBig,
},
{
title: "Emails",
url: "/emails",
icon: Mail,
},
{
title: "Templates",
url: "/templates",
icon: LayoutTemplate,
},
];
// Marketing items
const marketingItems = [
{
title: "Contacts",
url: "/contacts",
icon: BookUser,
},
{
title: "Campaigns",
url: "/campaigns",
icon: Volume2,
},
];
// Settings items
const settingsItems = [
{
title: "Domains",
url: "/domains",
icon: Globe,
},
{
title: "Developer settings",
url: "/dev-settings",
icon: Code,
},
{
title: "Settings",
url: "/settings",
icon: Cog,
},
// TODO: Add conditional logic for Admin item based on isSelfHosted() || session?.user.isAdmin
{
title: "Admin",
url: "/admin",
icon: Server,
isAdmin: true,
isSelfHosted: true,
},
];
export function AppSidebar() {
const { data: session } = useSession();
const { state, open } = useSidebar();
const pathname = usePathname();
return (
<Sidebar collapsible="icon" variant="sidebar">
<SidebarHeader>
<SidebarGroupLabel>
<div className="flex items-center gap-2">
<span className="text-lg font-semibold text-foreground">
Unsend
</span>
<Badge variant="outline">Beta</Badge>
</div>
</SidebarGroupLabel>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>
<span>General</span>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{generalItems.map((item) => {
const isActive = pathname?.startsWith(item.url);
return (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
asChild
tooltip={item.title}
isActive={isActive}
>
<Link href={item.url}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupLabel>
<span>Marketing</span>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{marketingItems.map((item) => {
const isActive = pathname?.startsWith(item.url);
return (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
asChild
tooltip={item.title}
isActive={isActive}
className="text-sidebar-foreground"
>
<Link href={item.url}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup>
<SidebarGroupLabel>
<span>Settings</span>
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{settingsItems.map((item) => {
const isActive = pathname?.startsWith(item.url);
if (item.isAdmin && !session?.user.isAdmin) {
return null;
}
if (item.isSelfHosted && !isSelfHosted()) {
return null;
}
return (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
asChild
tooltip={item.title}
isActive={isActive}
>
<Link href={item.url}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
);
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild tooltip="Docs">
<Link href="https://docs.unsend.dev" target="_blank">
<BookOpenText />
<span>Docs</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
<NavUser
user={{
name: session?.user.name || "",
email: session?.user.email || "",
avatar: session?.user.image || "",
}}
/>
</SidebarFooter>
</Sidebar>
);
}
export function NavUser({
user,
}: {
user: {
name?: string | null;
email?: string | null;
avatar?: string | null;
};
}) {
const { isMobile } = useSidebar();
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar className="h-8 w-8 rounded-lg">
{user.avatar ? (
<AvatarImage
src={user.avatar}
alt={user.name ?? user.email ?? ""}
/>
) : null}
<AvatarFallback className="rounded-lg capitalize">
{user.name?.charAt(0) ?? user.email?.charAt(0) ?? ""}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">
{user.name ?? user.email ?? ""}
</span>
<span className="truncate text-xs text-muted-foreground">
{user.name ? user.email : ""}
</span>
</div>
<MoreVerticalIcon className="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-xl"
side={isMobile ? "bottom" : "top"}
sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg">
{user.avatar ? (
<AvatarImage
src={user.avatar}
alt={user.name ?? user.email ?? ""}
/>
) : null}
<AvatarFallback className="rounded-lg capitalize">
{user.name?.charAt(0) ?? user.email?.charAt(0) ?? ""}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">
{user.name ?? user.email ?? ""}
</span>
<span className="truncate text-xs text-muted-foreground">
{user.name ? user.email : ""}
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<Link href="/settings/team">
<UsersIcon />
Team
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href="/settings">
<GaugeIcon />
Usage
</Link>
</DropdownMenuItem>
<div className="px-2 py-0.5">
<ThemeSwitcher />
</div>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => signOut()}>
<LogOutIcon />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}