fosrl.pangolin/src/components/ProfileIcon.tsx

185 lines
8.1 KiB
TypeScript
Raw Normal View History

2024-12-26 19:33:56 -05:00
"use client";
2025-01-01 21:41:31 -05:00
import { createApiClient } from "@app/lib/api";
2024-12-26 19:33:56 -05:00
import { Avatar, AvatarFallback } from "@app/components/ui/avatar";
import { Button } from "@app/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger
} from "@app/components/ui/dropdown-menu";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
2025-03-23 12:37:57 -04:00
import { formatAxiosError } from "@app/lib/api";
2024-12-26 19:33:56 -05:00
import { Laptop, LogOut, Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useUserContext } from "@app/hooks/useUserContext";
import Disable2FaForm from "./Disable2FaForm";
import SecurityKeyForm from "./SecurityKeyForm";
2025-07-13 21:43:09 -07:00
import Enable2FaDialog from "./Enable2FaDialog";
2025-03-23 12:37:57 -04:00
import SupporterStatus from "./SupporterStatus";
2025-04-29 22:59:38 -04:00
import { UserType } from "@server/types/UserTypes";
2025-05-04 15:11:42 +00:00
import LocaleSwitcher from '@app/components/LocaleSwitcher';
import { useTranslations } from "next-intl";
2025-05-04 15:11:42 +00:00
2024-12-26 19:33:56 -05:00
export default function ProfileIcon() {
const { setTheme, theme } = useTheme();
const { env } = useEnvContext();
const api = createApiClient({ env });
const { user, updateUser } = useUserContext();
const router = useRouter();
const [userTheme, setUserTheme] = useState<"light" | "dark" | "system">(
theme as "light" | "dark" | "system"
);
const [openEnable2fa, setOpenEnable2fa] = useState(false);
const [openDisable2fa, setOpenDisable2fa] = useState(false);
const [openSecurityKey, setOpenSecurityKey] = useState(false);
2024-12-26 19:33:56 -05:00
const t = useTranslations();
2024-12-26 19:33:56 -05:00
function getInitials() {
return (user.name || user.email || user.username)
2025-04-20 15:50:17 -04:00
.substring(0, 1)
.toUpperCase();
2024-12-26 19:33:56 -05:00
}
function handleThemeChange(theme: "light" | "dark" | "system") {
setUserTheme(theme);
setTheme(theme);
}
function logout() {
api.post("/auth/logout")
.catch((e) => {
console.error(t('logoutError'), e);
2024-12-26 19:33:56 -05:00
toast({
title: t('logoutError'),
description: formatAxiosError(e, t('logoutError'))
2024-12-26 19:33:56 -05:00
});
})
.then(() => {
router.push("/auth/login");
2025-01-11 15:10:02 -05:00
router.refresh();
2024-12-26 19:33:56 -05:00
});
}
return (
<>
2025-07-13 21:43:09 -07:00
<Enable2FaDialog open={openEnable2fa} setOpen={setOpenEnable2fa} />
2024-12-26 19:33:56 -05:00
<Disable2FaForm open={openDisable2fa} setOpen={setOpenDisable2fa} />
<SecurityKeyForm open={openSecurityKey} setOpen={setOpenSecurityKey} />
2024-12-26 19:33:56 -05:00
2025-04-18 11:36:34 -04:00
<div className="flex items-center md:gap-2 grow min-w-0 gap-2 md:gap-0">
2025-04-12 15:04:32 -04:00
<span className="truncate max-w-full font-medium min-w-0">
{user.name || user.email || user.username}
2025-04-12 15:04:32 -04:00
</span>
2024-12-26 19:33:56 -05:00
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="relative h-10 w-10 rounded-full"
>
<Avatar className="h-9 w-9">
<AvatarFallback>{getInitials()}</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-56"
align="start"
forceMount
>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">
{t('signingAs')}
2024-12-26 19:33:56 -05:00
</p>
<p className="text-xs leading-none text-muted-foreground">
{user.name || user.email || user.username}
2024-12-26 19:33:56 -05:00
</p>
</div>
2025-04-20 15:50:17 -04:00
{user.serverAdmin ? (
2024-12-26 19:33:56 -05:00
<p className="text-xs leading-none text-muted-foreground mt-2">
{t('serverAdmin')}
2024-12-26 19:33:56 -05:00
</p>
2025-04-20 15:50:17 -04:00
) : (
<p className="text-xs leading-none text-muted-foreground mt-2">
{user.idpName || t('idpNameInternal')}
2025-04-20 15:50:17 -04:00
</p>
2024-12-26 19:33:56 -05:00
)}
</DropdownMenuLabel>
<DropdownMenuSeparator />
2025-04-29 22:59:38 -04:00
{user?.type === UserType.Internal && (
<>
{!user.twoFactorEnabled && (
<DropdownMenuItem
onClick={() => setOpenEnable2fa(true)}
>
<span>{t('otpEnable')}</span>
2025-04-29 22:59:38 -04:00
</DropdownMenuItem>
)}
{user.twoFactorEnabled && (
<DropdownMenuItem
onClick={() => setOpenDisable2fa(true)}
>
<span>{t('otpDisable')}</span>
2025-04-29 22:59:38 -04:00
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={() => setOpenSecurityKey(true)}
>
<span>{t('securityKeyManage')}</span>
</DropdownMenuItem>
2025-04-29 22:59:38 -04:00
<DropdownMenuSeparator />
</>
2024-12-26 19:33:56 -05:00
)}
<DropdownMenuLabel>{t("theme")}</DropdownMenuLabel>
2024-12-26 19:33:56 -05:00
{(["light", "dark", "system"] as const).map(
(themeOption) => (
<DropdownMenuItem
key={themeOption}
onClick={() =>
handleThemeChange(themeOption)
}
>
{themeOption === "light" && (
<Sun className="mr-2 h-4 w-4" />
)}
{themeOption === "dark" && (
<Moon className="mr-2 h-4 w-4" />
)}
{themeOption === "system" && (
<Laptop className="mr-2 h-4 w-4" />
)}
<span className="capitalize">
{t(themeOption)}
2024-12-26 19:33:56 -05:00
</span>
{userTheme === themeOption && (
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<span className="h-2 w-2 rounded-full bg-primary"></span>
</span>
)}
</DropdownMenuItem>
)
)}
2025-05-04 15:11:42 +00:00
<DropdownMenuSeparator />
<LocaleSwitcher />
2024-12-26 19:33:56 -05:00
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => logout()}>
2025-01-04 20:22:01 -05:00
{/* <LogOut className="mr-2 h-4 w-4" /> */}
<span>{t('logout')}</span>
2024-12-26 19:33:56 -05:00
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</>
);
}