mirror of
https://github.com/massbug/judge4c.git
synced 2026-10-15 15:17:31 +00:00
feat(user-avatar): refactor avatar component into user-avatar with improved structure
This commit is contained in:
@@ -1,77 +0,0 @@
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { LogOutIcon } from "lucide-react";
|
||||
import { auth, signOut } from "@/lib/auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import LogInButton from "@/components/log-in-button";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import { SettingsButton } from "@/components/settings-button";
|
||||
|
||||
const UserAvatar = ({ image, name }: { image: string; name: string }) => (
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={image} alt={name} />
|
||||
<Skeleton className="h-full w-full" />
|
||||
</Avatar>
|
||||
);
|
||||
|
||||
async function handleLogOut() {
|
||||
"use server";
|
||||
await signOut();
|
||||
}
|
||||
|
||||
export async function AvatarButton() {
|
||||
const session = await auth();
|
||||
const t = await getTranslations("AvatarButton");
|
||||
const isLoggedIn = !!session?.user;
|
||||
const image = session?.user?.image ?? "/shadcn.jpg";
|
||||
const name = session?.user?.name ?? "unknown";
|
||||
const email = session?.user?.email ?? "unknwon@example.com";
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<UserAvatar image={image} name={name} />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
||||
align="end"
|
||||
sideOffset={8}
|
||||
>
|
||||
{!isLoggedIn ? (
|
||||
<DropdownMenuGroup>
|
||||
<SettingsButton />
|
||||
<LogInButton />
|
||||
</DropdownMenuGroup>
|
||||
) : (
|
||||
<>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<UserAvatar image={image} name={name} />
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">{name}</span>
|
||||
<span className="truncate text-xs">{email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<SettingsButton />
|
||||
<DropdownMenuItem onClick={handleLogOut}>
|
||||
<LogOutIcon />
|
||||
{t("LogOut")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { LogIn } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
export default function LogInButton() {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const t = useTranslations("AvatarButton");
|
||||
|
||||
const handleLogIn = () => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set("redirectTo", pathname);
|
||||
router.push(`/sign-in?${params.toString()}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuItem onClick={handleLogIn}>
|
||||
<LogIn />
|
||||
{t("LogIn")}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { useSettingsStore } from "@/stores/useSettingsStore";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
|
||||
export function SettingsButton() {
|
||||
const t = useTranslations("AvatarButton");
|
||||
const t = useTranslations("UserAvatar");
|
||||
const { setDialogOpen } = useSettingsStore();
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { LogIn, LogOutIcon } from "lucide-react";
|
||||
import { auth, signIn, signOut } from "@/lib/auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { SettingsButton } from "@/components/settings-button";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
|
||||
const handleLogIn = async () => {
|
||||
"use server";
|
||||
await signIn();
|
||||
};
|
||||
|
||||
const handleLogOut = async () => {
|
||||
"use server";
|
||||
await signOut();
|
||||
};
|
||||
|
||||
interface UserAvatarIconProps {
|
||||
image?: string | null;
|
||||
name?: string | null;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const UserAvatarIcon = ({ image, name, className }: UserAvatarIconProps) => {
|
||||
return (
|
||||
<Avatar className={cn("h-8 w-8 cursor-pointer", className)}>
|
||||
<AvatarImage src={image ?? undefined} />
|
||||
<AvatarFallback>{name?.charAt(0) ?? "U"}</AvatarFallback>
|
||||
</Avatar>
|
||||
);
|
||||
};
|
||||
|
||||
interface UserProfileInfoProps {
|
||||
name?: string | null;
|
||||
email?: string | null;
|
||||
}
|
||||
|
||||
const UserProfileInfo = ({ name, email }: UserProfileInfoProps) => {
|
||||
return (
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">{name ?? "undefined"}</span>
|
||||
<span className="truncate text-xs">{email ?? "undefined"}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const UserAvatar = async () => {
|
||||
const session = await auth();
|
||||
const user = session?.user;
|
||||
const isLoggedIn = !!user;
|
||||
const t = await getTranslations("UserAvatar");
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<UserAvatarIcon image={user?.image} name={user?.name} />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
||||
align="end"
|
||||
sideOffset={8}
|
||||
>
|
||||
{!isLoggedIn ? (
|
||||
<DropdownMenuGroup>
|
||||
<SettingsButton />
|
||||
<DropdownMenuItem onClick={handleLogIn}>
|
||||
<LogIn />
|
||||
{t("LogIn")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
) : (
|
||||
<>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<UserAvatarIcon image={user.image} name={user.name} />
|
||||
<UserProfileInfo name={user.name} email={user.email} />
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<SettingsButton />
|
||||
<DropdownMenuItem onClick={handleLogOut}>
|
||||
<LogOutIcon />
|
||||
{t("LogOut")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
const UserAvatarSkeleton = () => {
|
||||
return <Skeleton className="h-8 w-8 rounded-full" />;
|
||||
};
|
||||
|
||||
export { UserAvatar, UserAvatarSkeleton };
|
||||
Reference in New Issue
Block a user