feat(lovart-home): add hover avatar dropdown menu
This commit is contained in:
parent
310dd115b2
commit
b30ebc58b9
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
|
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn, signOut } from "next-auth/react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import useSWRInfinite from "swr/infinite";
|
import useSWRInfinite from "swr/infinite";
|
||||||
import {
|
import {
|
||||||
@ -12,11 +12,12 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
Clapperboard,
|
Clapperboard,
|
||||||
Folder,
|
Folder,
|
||||||
Heart,
|
|
||||||
Home,
|
Home,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
Info,
|
Info,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
LogOut,
|
||||||
|
Mail,
|
||||||
Monitor,
|
Monitor,
|
||||||
Palette,
|
Palette,
|
||||||
Plus,
|
Plus,
|
||||||
@ -26,6 +27,7 @@ import {
|
|||||||
Star,
|
Star,
|
||||||
Upload,
|
Upload,
|
||||||
User,
|
User,
|
||||||
|
Users,
|
||||||
X,
|
X,
|
||||||
Zap,
|
Zap,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@ -161,6 +163,12 @@ const PROMPT_TAGS = [
|
|||||||
const MASONRY_HEIGHTS = [420, 290, 340, 470, 300, 360, 430, 320];
|
const MASONRY_HEIGHTS = [420, 290, 340, 470, 300, 360, 430, 320];
|
||||||
const FEATURED_PAGE_SIZE = 24;
|
const FEATURED_PAGE_SIZE = 24;
|
||||||
|
|
||||||
|
function getUserInitials(user: HomeUser | null) {
|
||||||
|
const source = (user?.name || user?.email || "mu").trim();
|
||||||
|
if (!source) return "mu";
|
||||||
|
return source.slice(0, 2).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
function formatRelativeTime(value?: string) {
|
function formatRelativeTime(value?: string) {
|
||||||
if (!value) return "更新于今天";
|
if (!value) return "更新于今天";
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
@ -179,6 +187,29 @@ function formatPrice(currency: string, value: number) {
|
|||||||
return `${currency}${value}`;
|
return `${currency}${value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AccountMenuItem({
|
||||||
|
icon: Icon,
|
||||||
|
label,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
icon: typeof User;
|
||||||
|
label: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
className="flex w-full items-center gap-3 rounded-2xl px-3 py-2 text-left text-[15px] font-medium text-black/80 hover:bg-black/5"
|
||||||
|
>
|
||||||
|
<span className="flex h-9 w-9 items-center justify-center rounded-xl bg-black/5 text-black/70">
|
||||||
|
<Icon className="h-[18px] w-[18px]" />
|
||||||
|
</span>
|
||||||
|
<span className="flex-1">{label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function LovartHome({
|
export function LovartHome({
|
||||||
user,
|
user,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
@ -669,6 +700,12 @@ export function LovartHome({
|
|||||||
previewCursorVisible ? " |" : ""
|
previewCursorVisible ? " |" : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const userInitials = useMemo(() => getUserInitials(user), [user]);
|
||||||
|
const userDisplayName = useMemo(() => {
|
||||||
|
if (!user) return "";
|
||||||
|
return (user.name || user.email || "用户").toString();
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-svh bg-[#f4f4f4] text-[#171717]">
|
<div className="min-h-svh bg-[#f4f4f4] text-[#171717]">
|
||||||
<header className="fixed left-0 top-0 z-40 h-[68px] w-full bg-[#f4f4f4]/90 backdrop-blur">
|
<header className="fixed left-0 top-0 z-40 h-[68px] w-full bg-[#f4f4f4]/90 backdrop-blur">
|
||||||
@ -754,9 +791,94 @@ export function LovartHome({
|
|||||||
</HoverCard>
|
</HoverCard>
|
||||||
|
|
||||||
{isAuthenticated ? (
|
{isAuthenticated ? (
|
||||||
<button className="flex h-8 w-8 items-center justify-center rounded-full bg-[#d4551f] text-[14px] font-semibold text-white">
|
<HoverCard openDelay={80} closeDelay={120}>
|
||||||
{(user?.name || user?.email || "mu").slice(0, 2).toLowerCase()}
|
<HoverCardTrigger asChild>
|
||||||
</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex h-8 w-8 items-center justify-center rounded-full bg-[#d4551f] text-[14px] font-semibold text-white"
|
||||||
|
aria-label="Account menu"
|
||||||
|
>
|
||||||
|
{userInitials}
|
||||||
|
</button>
|
||||||
|
</HoverCardTrigger>
|
||||||
|
<HoverCardContent
|
||||||
|
align="end"
|
||||||
|
sideOffset={12}
|
||||||
|
className="w-[360px] rounded-3xl border border-black/10 bg-white p-0 shadow-xl"
|
||||||
|
>
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="rounded-3xl border border-black/10 bg-white p-4">
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<div className="flex h-14 w-14 items-center justify-center rounded-full bg-[#d4551f] text-[18px] font-semibold text-white">
|
||||||
|
{userInitials}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3 flex items-center justify-between gap-3">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="truncate text-[18px] font-semibold text-black">
|
||||||
|
{userDisplayName}
|
||||||
|
</div>
|
||||||
|
<div className="truncate text-[14px] text-black/45">
|
||||||
|
{user?.email || ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="h-9 rounded-2xl bg-[#2f3542] px-6 text-[14px] text-white hover:bg-[#242a36]"
|
||||||
|
onClick={() => setUpgradeDialogOpen(true)}
|
||||||
|
>
|
||||||
|
升级
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 border-t border-black/10 pt-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex w-full items-center justify-between rounded-2xl px-1 py-1 text-[15px] font-semibold text-black/80 hover:bg-black/5"
|
||||||
|
onClick={() => setUpgradeDialogOpen(true)}
|
||||||
|
>
|
||||||
|
<span>积分</span>
|
||||||
|
<span className="inline-flex items-center gap-2 text-[15px] font-semibold text-black/60">
|
||||||
|
{membershipData?.summary?.currentCredits ?? 0}
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3 space-y-1">
|
||||||
|
<AccountMenuItem
|
||||||
|
icon={Users}
|
||||||
|
label="创建团队"
|
||||||
|
onClick={() => toast.info("团队功能后续开放")}
|
||||||
|
/>
|
||||||
|
<AccountMenuItem
|
||||||
|
icon={User}
|
||||||
|
label="账户管理"
|
||||||
|
onClick={() => toast.info("账户管理后续开放")}
|
||||||
|
/>
|
||||||
|
<AccountMenuItem
|
||||||
|
icon={Info}
|
||||||
|
label="使用指南"
|
||||||
|
onClick={() => toast.info("使用指南后续开放")}
|
||||||
|
/>
|
||||||
|
<AccountMenuItem
|
||||||
|
icon={Mail}
|
||||||
|
label="联系我们"
|
||||||
|
onClick={() => toast.info("联系我们后续开放")}
|
||||||
|
/>
|
||||||
|
<AccountMenuItem
|
||||||
|
icon={LogOut}
|
||||||
|
label="退出登录"
|
||||||
|
onClick={() => {
|
||||||
|
void signOut({ redirect: true, callbackUrl: "/" });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HoverCardContent>
|
||||||
|
</HoverCard>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user