mirror of
https://github.com/massbug/judge4c.git
synced 2025-05-17 23:12:23 +00:00
feat(user-avatar): refactor avatar component into user-avatar with improved structure
This commit is contained in:
parent
a3ef5d88e6
commit
500113fe8f
@ -7,7 +7,7 @@
|
||||
"Dark": "Dark"
|
||||
}
|
||||
},
|
||||
"AvatarButton": {
|
||||
"UserAvatar": {
|
||||
"Settings": "Settings",
|
||||
"LogIn": "LogIn",
|
||||
"LogOut": "LogOut"
|
||||
@ -109,7 +109,8 @@
|
||||
"description": "Enter your email below to sign in to your account",
|
||||
"or": "Or",
|
||||
"noAccount": "Don't have an account?",
|
||||
"signUp": "Sign up"
|
||||
"signUp": "Sign up",
|
||||
"oauth": "Sign in with {provider}"
|
||||
},
|
||||
"signInWithCredentials": {
|
||||
"userNotFound": "User not found.",
|
||||
@ -215,4 +216,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"Dark": "深色"
|
||||
}
|
||||
},
|
||||
"AvatarButton": {
|
||||
"UserAvatar": {
|
||||
"Settings": "设置",
|
||||
"LogIn": "登录",
|
||||
"LogOut": "登出"
|
||||
@ -109,7 +109,8 @@
|
||||
"description": "请输入你的邮箱以登录账户",
|
||||
"or": "或者",
|
||||
"noAccount": "还没有账户?",
|
||||
"signUp": "注册"
|
||||
"signUp": "注册",
|
||||
"oauth": "使用 {provider} 登录"
|
||||
},
|
||||
"signInWithCredentials": {
|
||||
"userNotFound": "未找到用户。",
|
||||
@ -215,4 +216,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 (
|
||||
|
108
src/components/user-avatar.tsx
Normal file
108
src/components/user-avatar.tsx
Normal file
@ -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 };
|
Loading…
Reference in New Issue
Block a user