Skip to content

Commit c49880b

Browse files
committed
profile button
1 parent e9a7d26 commit c49880b

File tree

3 files changed

+132
-13
lines changed

3 files changed

+132
-13
lines changed

apps/dashboard/components/layout/category-sidebar.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
getContextConfig,
2424
getDefaultCategory,
2525
} from "./navigation/navigation-config";
26-
import { SignOutButton } from "./sign-out-button";
26+
import { ProfileButton } from "./profile-button";
2727
import { ThemeToggle } from "./theme-toggle";
2828

2929
const HelpDialog = dynamic(
@@ -103,29 +103,34 @@ export function CategorySidebar({
103103
const Icon = category.icon;
104104
const isActive = activeCategory === category.id;
105105
const isLast = idx === categories.length - 1;
106+
const shouldShowBorder = isActive && !isLast;
107+
const borderClass = shouldShowBorder ? "border-accent" : "";
108+
const hoverClass = isActive ? "" : "hover:bg-sidebar-accent-brighter";
109+
const boxClass = isLast
110+
? "box-content border-border border-b"
111+
: "box-content border-transparent";
112+
106113
return (
107114
<Tooltip delayDuration={500} key={category.id}>
108115
<TooltipTrigger asChild>
109116
<button
110117
className={cn(
111-
isActive && !isLast && "border-accent",
118+
borderClass,
112119
"relative flex h-10 w-full cursor-pointer items-center justify-center",
113120
"focus:outline-none",
114-
!isActive && "hover:bg-sidebar-accent-brighter",
115-
isLast
116-
? "box-content border-border border-b"
117-
: "box-content border-transparent"
121+
hoverClass,
122+
boxClass
118123
)}
119124
onClick={() => onCategoryChangeAction?.(category.id)}
120125
type="button"
121126
>
122-
{isActive && (
127+
{isActive ? (
123128
<div
124129
className={cn(
125130
"absolute top-0 left-0 z-[-1] box-border h-full w-full bg-sidebar-accent-brighter"
126131
)}
127132
/>
128-
)}
133+
) : null}
129134
<Icon
130135
className={cn(
131136
"size-5",
@@ -161,12 +166,12 @@ export function CategorySidebar({
161166
type="button"
162167
variant="ghost"
163168
>
164-
<InfoIcon className="size-4" weight="duotone" />
169+
<InfoIcon className="size-5" weight="duotone" />
165170
</Button>
166171
</div>
167172

168173
<div className="flex justify-center">
169-
<SignOutButton />
174+
<ProfileButton />
170175
</div>
171176
</div>
172177

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"use client";
2+
3+
import { authClient } from "@databuddy/auth/client";
4+
import { GearIcon, SignOutIcon } from "@phosphor-icons/react";
5+
import { useRouter } from "next/navigation";
6+
import { useState } from "react";
7+
import { toast } from "sonner";
8+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
9+
import {
10+
DropdownMenu,
11+
DropdownMenuContent,
12+
DropdownMenuItem,
13+
DropdownMenuLabel,
14+
DropdownMenuSeparator,
15+
DropdownMenuTrigger,
16+
} from "@/components/ui/dropdown-menu";
17+
import {
18+
Tooltip,
19+
TooltipContent,
20+
TooltipTrigger,
21+
} from "@/components/ui/tooltip";
22+
23+
export function ProfileButton() {
24+
const [isLoggingOut, setIsLoggingOut] = useState(false);
25+
const [isOpen, setIsOpen] = useState(false);
26+
const router = useRouter();
27+
const { data: session, isPending: isSessionLoading } =
28+
authClient.useSession();
29+
30+
const handleLogout = async () => {
31+
setIsLoggingOut(true);
32+
setIsOpen(false);
33+
await authClient.signOut({
34+
fetchOptions: {
35+
onSuccess: () => {
36+
toast.success("Logged out successfully");
37+
router.push("/login");
38+
},
39+
onError: (error) => {
40+
router.push("/login");
41+
toast.error(error.error.message || "Failed to log out");
42+
},
43+
},
44+
});
45+
setIsLoggingOut(false);
46+
};
47+
48+
const handleSettings = () => {
49+
setIsOpen(false);
50+
router.push("/settings/account");
51+
};
52+
53+
const user = session?.user;
54+
const userInitials = user?.name
55+
? user.name
56+
.split(" ")
57+
.map((n) => n[0])
58+
.join("")
59+
.toUpperCase()
60+
.slice(0, 2)
61+
: user?.email?.[0]?.toUpperCase() || "U";
62+
63+
return (
64+
<DropdownMenu onOpenChange={setIsOpen} open={isOpen}>
65+
<Tooltip>
66+
<TooltipTrigger asChild>
67+
<DropdownMenuTrigger
68+
aria-label="Profile menu"
69+
className="flex size-8 items-center justify-center rounded-full outline-hidden transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
70+
disabled={isLoggingOut || isSessionLoading}
71+
>
72+
<Avatar className="size-8">
73+
<AvatarImage
74+
alt={user?.name || "User"}
75+
src={user?.image || undefined}
76+
/>
77+
<AvatarFallback className="bg-primary text-primary-foreground text-xs">
78+
{userInitials}
79+
</AvatarFallback>
80+
</Avatar>
81+
</DropdownMenuTrigger>
82+
</TooltipTrigger>
83+
<TooltipContent side="right">
84+
<p>Profile menu</p>
85+
</TooltipContent>
86+
</Tooltip>
87+
88+
<DropdownMenuContent align="start" className="w-56" side="right">
89+
<DropdownMenuLabel>
90+
<div className="flex flex-col space-y-1">
91+
<p className="font-medium text-sm leading-none">{user?.name}</p>
92+
<p className="text-muted-foreground text-xs leading-none">
93+
{user?.email}
94+
</p>
95+
</div>
96+
</DropdownMenuLabel>
97+
<DropdownMenuSeparator />
98+
<DropdownMenuItem onClick={handleSettings}>
99+
<GearIcon weight="duotone" />
100+
Settings
101+
</DropdownMenuItem>
102+
<DropdownMenuSeparator />
103+
<DropdownMenuItem
104+
disabled={isLoggingOut}
105+
onClick={handleLogout}
106+
variant="destructive"
107+
>
108+
<SignOutIcon weight="duotone" />
109+
{isLoggingOut ? "Signing out..." : "Sign out"}
110+
</DropdownMenuItem>
111+
</DropdownMenuContent>
112+
</DropdownMenu>
113+
);
114+
}

apps/dashboard/components/layout/sidebar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ type NavigationConfig = {
3434
export function Sidebar() {
3535
const pathname = usePathname();
3636
const [isMobileOpen, setIsMobileOpen] = useState(false);
37-
const [selectedCategory, setSelectedCategory] = useState<
38-
string | undefined
39-
>(undefined);
37+
const [selectedCategory, setSelectedCategory] = useState<string | undefined>(
38+
undefined
39+
);
4040
const { websites, isLoading: isLoadingWebsites } = useWebsites();
4141
const accordionStates = useAccordionStates();
4242
const sidebarRef = useRef<HTMLDivElement>(null);

0 commit comments

Comments
 (0)