add avatar menu in side bar
This commit is contained in:
@@ -19,6 +19,13 @@ import {
|
||||
BookOpenText,
|
||||
ChartColumnBig,
|
||||
ChartArea,
|
||||
BellIcon,
|
||||
CreditCardIcon,
|
||||
LogOutIcon,
|
||||
MoreVerticalIcon,
|
||||
UserCircleIcon,
|
||||
UsersIcon,
|
||||
GaugeIcon,
|
||||
} from "lucide-react";
|
||||
import { signOut } from "next-auth/react";
|
||||
|
||||
@@ -41,6 +48,16 @@ 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 = [
|
||||
@@ -211,12 +228,6 @@ export function AppSidebar() {
|
||||
<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">
|
||||
@@ -225,12 +236,116 @@ export function AppSidebar() {
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem className="px-2">
|
||||
<ThemeSwitcher />
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ export const ThemeSwitcher = () => {
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-between">
|
||||
<p className="text-sm text-sidebar-foreground flex items-center gap-2">
|
||||
<p className="text-sm text-popover-foreground flex items-center gap-2">
|
||||
<SunMoonIcon className="h-4 w-4" />
|
||||
Theme
|
||||
</p>
|
||||
|
@@ -31,6 +31,7 @@
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@radix-ui/react-accordion": "^1.2.8",
|
||||
"@radix-ui/react-avatar": "^1.1.9",
|
||||
"@radix-ui/react-dialog": "^1.1.11",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.12",
|
||||
"@radix-ui/react-label": "^2.1.4",
|
||||
|
50
packages/ui/src/avatar.tsx
Normal file
50
packages/ui/src/avatar.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
||||
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ComponentRef<typeof AvatarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Avatar.displayName = AvatarPrimitive.Root.displayName;
|
||||
|
||||
const AvatarImage = React.forwardRef<
|
||||
React.ComponentRef<typeof AvatarPrimitive.Image>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
||||
|
||||
const AvatarFallback = React.forwardRef<
|
||||
React.ComponentRef<typeof AvatarPrimitive.Fallback>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback };
|
@@ -12,7 +12,7 @@
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--popover: 220 2% 96%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--popover-foreground: 234 16% 35%;
|
||||
|
||||
--primary: 200 65% 14%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
@@ -62,7 +62,7 @@
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 240 21% 15%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--popover-foreground: 226 64% 88%;
|
||||
|
||||
--primary: 220 23% 95%;
|
||||
--primary-foreground: 240 23% 9%;
|
||||
|
75
pnpm-lock.yaml
generated
75
pnpm-lock.yaml
generated
@@ -527,6 +527,9 @@ importers:
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8(@types/react-dom@19.1.2)(@types/react@19.1.2)(react-dom@19.1.0)(react@19.1.0)
|
||||
'@radix-ui/react-avatar':
|
||||
specifier: ^1.1.9
|
||||
version: 1.1.9(@types/react-dom@19.1.2)(@types/react@19.1.2)(react-dom@19.1.0)(react@19.1.0)
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.1.11
|
||||
version: 1.1.11(@types/react-dom@19.1.2)(@types/react@19.1.2)(react-dom@19.1.0)(react@19.1.0)
|
||||
@@ -4023,6 +4026,30 @@ packages:
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-avatar@1.1.9(@types/react-dom@19.1.2)(@types/react@19.1.2)(react-dom@19.1.0)(react@19.1.0):
|
||||
resolution: {integrity: sha512-10tQokfvZdFvnvDkcOJPjm2pWiP8A0R4T83MoD7tb15bC/k2GU7B1YBuzJi8lNQ8V1QqhP8ocNqp27ByZaNagQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
|
||||
'@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.2)(@types/react@19.1.2)(react-dom@19.1.0)(react@19.1.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||
'@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.2)(react@19.1.0)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
|
||||
'@types/react': 19.1.2
|
||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-collapsible@1.1.1(@types/react@19.1.2)(react-dom@19.1.0)(react@19.1.0):
|
||||
resolution: {integrity: sha512-1///SnrfQHJEofLokyczERxQbWfCGQlQ2XsCZMucVs6it+lq9iw4vXy+uDn1edlb58cOZOWSldnfPAYcT4O/Yg==}
|
||||
peerDependencies:
|
||||
@@ -4725,6 +4752,26 @@ packages:
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-primitive@2.1.2(@types/react-dom@19.1.2)(@types/react@19.1.2)(react-dom@19.1.0)(react@19.1.0):
|
||||
resolution: {integrity: sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.2.2(@types/react@19.1.2)(react@19.1.0)
|
||||
'@types/react': 19.1.2
|
||||
'@types/react-dom': 19.1.2(@types/react@19.1.2)
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-progress@1.1.4(@types/react-dom@19.1.2)(@types/react@19.1.2)(react-dom@19.1.0)(react@19.1.0):
|
||||
resolution: {integrity: sha512-8rl9w7lJdcVPor47Dhws9mUHRHLE+8JEgyJRdNWCpGPa6HIlr3eh+Yn9gyx1CnCLbw5naHsI2gaO9dBWO50vzw==}
|
||||
peerDependencies:
|
||||
@@ -4889,6 +4936,20 @@ packages:
|
||||
react: 19.1.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-slot@1.2.2(@types/react@19.1.2)(react@19.1.0):
|
||||
resolution: {integrity: sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
|
||||
'@types/react': 19.1.2
|
||||
react: 19.1.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-switch@1.2.2(@types/react-dom@19.1.2)(@types/react@19.1.2)(react-dom@19.1.0)(react@19.1.0):
|
||||
resolution: {integrity: sha512-7Z8n6L+ifMIIYZ83f28qWSceUpkXuslI2FJ34+kDMTiyj91ENdpdQ7VCidrzj5JfwfZTeano/BnGBbu/jqa5rQ==}
|
||||
peerDependencies:
|
||||
@@ -5116,6 +5177,20 @@ packages:
|
||||
react: 19.1.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.2)(react@19.1.0):
|
||||
resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 19.1.2
|
||||
react: 19.1.0
|
||||
use-sync-external-store: 1.5.0(react@19.1.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.1.2)(react@19.1.0):
|
||||
resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
|
||||
peerDependencies:
|
||||
|
Reference in New Issue
Block a user