feat: add new theme (#157)

This commit is contained in:
KM Koushik
2025-05-06 22:00:50 +10:00
committed by GitHub
parent 2de7147cdf
commit b394c78be2
40 changed files with 1236 additions and 494 deletions

View File

@@ -0,0 +1,236 @@
"use client";
import {
BookUser,
Calendar,
Code,
Cog,
Globe,
Home,
Inbox,
LayoutDashboard,
LayoutTemplate,
LogOut,
Mail,
Search,
Server,
Settings,
Volume2,
BookOpenText,
ChartColumnBig,
ChartArea,
} 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";
// 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 onClick={() => signOut()} tooltip="Logout">
<LogOut />
<span>Logout</span>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild tooltip="Docs">
<Link href="https://docs.unsend.dev" target="_blank">
<BookOpenText />
<span>Docs</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem className="px-2">
<ThemeSwitcher />
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarFooter>
</Sidebar>
);
}

View File

@@ -123,11 +123,11 @@ export default function JoinTeam({
<DialogTitle>Accept Team Invitation</DialogTitle>
<DialogDescription>
Are you sure you want to join{" "}
<span className="font-semibold text-primary">
<span className="font-semibold text-foreground">
{selectedInvite?.team.name}
</span>
? You will be added as a{" "}
<span className="font-semibold text-primary lowercase">
<span className="font-semibold text-foreground lowercase">
{selectedInvite?.role.toLowerCase()}
</span>
.

View File

@@ -3,19 +3,22 @@ import { Button } from "@unsend/ui/src/button";
import { Monitor, Sun, Moon, SunMoonIcon } from "lucide-react";
export const ThemeSwitcher = () => {
const { theme, setTheme, systemTheme } = useTheme();
const { theme, setTheme } = useTheme();
return (
<div className="flex gap-2 items-center justify-between w-full">
<p className="text-sm text-muted-foreground flex items-center gap-2">
<div className="flex gap-2 items-center justify-between">
<p className="text-sm text-sidebar-foreground flex items-center gap-2">
<SunMoonIcon className="h-4 w-4" />
Theme
</p>
<div className="flex gap-2 border rounded-md p-0.5 ">
<div className="flex gap-2 border rounded-md p-1 ">
<Button
variant="ghost"
size="sm"
className={cn("p-0.5 h-5 w-5", theme === "system" ? "bg-muted" : "")}
className={cn(
"p-0.5 rounded-[0.20rem] h-5 w-5",
theme === "system" ? " bg-muted" : ""
)}
onClick={() => setTheme("system")}
>
<Monitor className="h-3 w-3" />
@@ -24,8 +27,8 @@ export const ThemeSwitcher = () => {
variant="ghost"
size="sm"
className={cn(
"p-0.5 h-5 w-5",
theme === "light" ? " bg-gray-200" : ""
"p-0.5 rounded-[0.20rem] h-5 w-5",
theme === "light" ? " bg-muted" : ""
)}
onClick={() => setTheme("light")}
>
@@ -34,7 +37,10 @@ export const ThemeSwitcher = () => {
<Button
variant="ghost"
size="sm"
className={cn("p-0.5 h-5 w-5", theme === "dark" ? "bg-muted" : "")}
className={cn(
"p-0.5 rounded-[0.20rem] h-5 w-5",
theme === "dark" ? "bg-muted" : ""
)}
onClick={() => setTheme("dark")}
>
<Moon className="h-3 w-3" />
@@ -43,3 +49,34 @@ export const ThemeSwitcher = () => {
</div>
);
};
export const MiniThemeSwitcher = () => {
const { theme, setTheme } = useTheme();
const cycleTheme = () => {
if (theme === "light") {
setTheme("dark");
} else if (theme === "dark") {
setTheme("system");
} else {
setTheme("light");
}
};
const renderIcon = () => {
switch (theme) {
case "light":
return <Sun className="h-4 w-4" />;
case "dark":
return <Moon className="h-4 w-4" />;
default:
return <Monitor className="h-4 w-4" />;
}
};
return (
<Button variant="ghost" size="icon" onClick={cycleTheme}>
{renderIcon()}
</Button>
);
};