diff --git a/apps/web/src/components/AppSideBar.tsx b/apps/web/src/components/AppSideBar.tsx index 247ef00..317c9ad 100644 --- a/apps/web/src/components/AppSideBar.tsx +++ b/apps/web/src/components/AppSideBar.tsx @@ -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() { - - signOut()} tooltip="Logout"> - - Logout - - @@ -225,12 +236,116 @@ export function AppSidebar() { - - - + ); } + +export function NavUser({ + user, +}: { + user: { + name?: string | null; + email?: string | null; + avatar?: string | null; + }; +}) { + const { isMobile } = useSidebar(); + + return ( + + + + + + + {user.avatar ? ( + + ) : null} + + {user.name?.charAt(0) ?? user.email?.charAt(0) ?? ""} + + +
+ + {user.name ?? user.email ?? ""} + + + {user.name ? user.email : ""} + +
+ +
+
+ + +
+ + {user.avatar ? ( + + ) : null} + + {user.name?.charAt(0) ?? user.email?.charAt(0) ?? ""} + + +
+ + {user.name ?? user.email ?? ""} + + + {user.name ? user.email : ""} + +
+
+
+ + + + + + Team + + + + + + Usage + + +
+ +
+
+ + signOut()}> + + Log out + +
+
+
+
+ ); +} diff --git a/apps/web/src/components/theme/ThemeSwitcher.tsx b/apps/web/src/components/theme/ThemeSwitcher.tsx index 6c6948b..e3a090e 100644 --- a/apps/web/src/components/theme/ThemeSwitcher.tsx +++ b/apps/web/src/components/theme/ThemeSwitcher.tsx @@ -7,7 +7,7 @@ export const ThemeSwitcher = () => { return (
-

+

Theme

diff --git a/packages/ui/package.json b/packages/ui/package.json index ca7877c..8139aeb 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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", diff --git a/packages/ui/src/avatar.tsx b/packages/ui/src/avatar.tsx new file mode 100644 index 0000000..274be97 --- /dev/null +++ b/packages/ui/src/avatar.tsx @@ -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, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/packages/ui/styles/globals.css b/packages/ui/styles/globals.css index e3e0785..1f33284 100644 --- a/packages/ui/styles/globals.css +++ b/packages/ui/styles/globals.css @@ -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%; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b0c4b6..b2172b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: