better-auth/apps/web/modules/saas/shared/components/UserMenu.tsx

177 lines
4.3 KiB
TypeScript
Raw Normal View History

2025-10-29 13:43:43 +05:00
"use client";
import { DropdownMenuSub } from "@radix-ui/react-dropdown-menu";
import { authClient } from "@repo/auth/client";
import { config } from "@repo/config";
import { useSession } from "@saas/auth/hooks/use-session";
import { UserAvatar } from "@shared/components/UserAvatar";
import { clearCache } from "@shared/lib/cache";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@ui/components/dropdown-menu";
import {
BookIcon,
HardDriveIcon,
HomeIcon,
LogOutIcon,
MoonIcon,
MoreVerticalIcon,
SettingsIcon,
SunIcon,
} from "lucide-react";
import { useTranslations } from "next-intl";
import { useTheme } from "next-themes";
import Link from "next/link";
import { useState } from "react";
export function UserMenu({ showUserName }: { showUserName?: boolean }) {
const t = useTranslations();
const { user } = useSession();
const { setTheme: setCurrentTheme, theme: currentTheme } = useTheme();
const [theme, setTheme] = useState<string>(currentTheme ?? "system");
const colorModeOptions = [
{
value: "system",
label: "System",
icon: HardDriveIcon,
},
{
value: "light",
label: "Light",
icon: SunIcon,
},
{
value: "dark",
label: "Dark",
icon: MoonIcon,
},
];
const onLogout = () => {
authClient.signOut({
fetchOptions: {
onSuccess: async () => {
await clearCache();
window.location.href = new URL(
config.auth.redirectAfterLogout,
window.location.origin,
).toString();
},
},
});
};
if (!user) {
return null;
}
const { name, email, image } = user;
return (
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<button
type="button"
className="flex cursor-pointer w-full items-center justify-between gap-2 rounded-lg outline-hidden focus-visible:ring-2 focus-visible:ring-primary md:w-[100%+1rem] md:px-2 md:py-1.5 md:hover:bg-primary/5"
aria-label="User menu"
>
<span className="flex items-center gap-2">
<UserAvatar name={name ?? ""} avatarUrl={image} />
{showUserName && (
<span className="text-left leading-tight">
<span className="font-medium text-sm">
{name}
</span>
<span className="block text-xs opacity-70">
{email}
</span>
</span>
)}
</span>
{showUserName && <MoreVerticalIcon className="size-4" />}
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>
{name}
<span className="block font-normal text-xs opacity-70">
{email}
</span>
</DropdownMenuLabel>
<DropdownMenuSeparator />
{/* Color mode selection */}
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<SunIcon className="mr-2 size-4" />
{t("app.userMenu.colorMode")}
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup
value={theme}
onValueChange={(value) => {
setTheme(value);
setCurrentTheme(value);
}}
>
{colorModeOptions.map((option) => (
<DropdownMenuRadioItem
key={option.value}
value={option.value}
>
<option.icon className="mr-2 size-4 opacity-50" />
{option.label}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link href="/app/settings/general">
<SettingsIcon className="mr-2 size-4" />
{t("app.userMenu.accountSettings")}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href="https://supastarter.dev/docs/nextjs">
<BookIcon className="mr-2 size-4" />
{t("app.userMenu.documentation")}
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href="/">
<HomeIcon className="mr-2 size-4" />
{t("app.userMenu.home")}
</Link>
</DropdownMenuItem>
<DropdownMenuItem onClick={onLogout}>
<LogOutIcon className="mr-2 size-4" />
{t("app.userMenu.logout")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}