"use client" import * as React from "react" import Link from "next/link" import {motion, useAnimation} from "motion/react" import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query" import {Avatar, AvatarFallback, AvatarImage} from "@/components/ui/avatar" import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb" import {useUser} from "@/contexts/user-context" import { ArrowRight, BookOpen, Camera, Edit, Globe, Info, Link2, Loader2, Lock, Mail, MapPin, Phone, Shield, Unlink, User as UserIcon } from "lucide-react" import {Button} from "@/components/ui/button" import {Input} from "@/components/ui/input" import {Separator} from "@/components/ui/separator" import {Textarea} from "@/components/ui/textarea" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/ui/select" import {Label} from "@/components/ui/label" import {AuthService} from "@/lib/services" import {UploadService} from "@/lib/services/upload/upload.service" import {ImageCrop, ImageCropApply, ImageCropContent, ImageCropReset} from "@/components/ui/image-crop" import {toast} from "sonner" export function ProfileMain() { const { user, loading, refetch } = useUser() const controls = useAnimation() const isAnimatingRef = React.useRef(false) const queryClient = useQueryClient() // 修改密码 State const [oldPassword, setOldPassword] = React.useState("") const [newPassword, setNewPassword] = React.useState("") const [confirmPassword, setConfirmPassword] = React.useState("") // 编辑个人资料 State const [isEditDialogOpen, setIsEditDialogOpen] = React.useState(false) const [nickname, setNickname] = React.useState("") const [email, setEmail] = React.useState("") const [bio, setBio] = React.useState("") const [phone, setPhone] = React.useState("") const [gender, setGender] = React.useState("secret") const [website, setWebsite] = React.useState("") const [location, setLocation] = React.useState("") const [avatarUrl, setAvatarUrl] = React.useState("") // 头像裁剪 State const [cropFile, setCropFile] = React.useState(null) const [isCropDialogOpen, setIsCropDialogOpen] = React.useState(false) const fileInputRef = React.useRef(null) // 初始化编辑表单数据 React.useEffect(() => { if (user) { setNickname(user.nickname || "") setEmail(user.email || "") setBio(user.bio || "") setPhone(user.phone || "") setGender(user.gender || "secret") setWebsite(user.website || "") setLocation(user.location || "") setAvatarUrl(user.avatar_url || "") } }, [user, isEditDialogOpen]) const changePasswordMutation = useMutation({ mutationFn: (req: any) => AuthService.changePassword(req), onSuccess: () => { toast.success("密码修改成功") setOldPassword("") setNewPassword("") setConfirmPassword("") void refetch() }, onError: (error: Error) => { toast.error(error.message || "修改密码失败,请重试") }, }) const handlePasswordChange = (e: React.FormEvent) => { e.preventDefault() if (newPassword !== confirmPassword) { toast.error("两次输入的新密码不一致") return } if (newPassword.length < 8) { toast.error("新密码长度不能少于 8 位") return } changePasswordMutation.mutate({ old_password: oldPassword, new_password: newPassword, }) } const updateProfileMutation = useMutation({ mutationFn: (req: any) => AuthService.updateProfile(req), onSuccess: () => { toast.success("个人信息修改成功") setIsEditDialogOpen(false) void refetch() }, onError: (error: Error) => { toast.error(error.message || "修改个人信息失败,请重试") }, }) const handleProfileSubmit = (e: React.FormEvent) => { e.preventDefault() updateProfileMutation.mutate({ nickname, email, avatar_url: avatarUrl, bio, phone, gender, website, location, }) } const externalAccountBindingsQuery = useQuery({ queryKey: ["auth", "external-accounts"], queryFn: () => AuthService.getExternalAccountBindings(), }) const publicAuthSourcesQuery = useQuery({ queryKey: ["auth", "public-sources"], queryFn: () => AuthService.getAuthSources(), }) const bindSourceMutation = useMutation({ mutationFn: async (sourceName: string) => { const { authorize_url } = await AuthService.getAuthorizeUrl(sourceName, "bind") sessionStorage.setItem("redirect_after_login", `${window.location.pathname}${window.location.search}`) window.location.href = authorize_url }, onError: (error: Error) => { toast.error(error.message || "绑定认证源失败") }, }) const handleAvatarClick = () => { if (isAnimatingRef.current) return isAnimatingRef.current = true controls.start({ rotate: [0, -20, 20, -20, 20, 0], transition: { duration: 0.5, ease: "easeInOut" } }) setTimeout(() => { isAnimatingRef.current = false }, 650) } const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file) { setCropFile(file) setIsCropDialogOpen(true) } } const handleCroppedImage = async (croppedBase64: string) => { try { const res = await UploadService.uploadBase64Image(croppedBase64, "avatar", "avatar.png") setAvatarUrl(`/f/${res.id}`) setIsCropDialogOpen(false) setCropFile(null) toast.success("头像上传成功,点击保存以生效") } catch (err: any) { toast.error(err.message || "上传头像失败") } } const getGenderLabel = (g?: string) => { switch (g) { case "male": return "男" case "female": return "女" case "secret": return "保密" default: return g || "保密" } } if (loading) { return (

个人资料

) } if (!user) { return (
未找到用户信息
) } return (
设置 个人资料
{/* 基本资料面板 */}

基本资料

您的个人账户基本信息

{(user.nickname || user.username).slice(0, 2).toUpperCase()}
账户
@{user.username}
昵称
{user.nickname || '未设置'}
用户ID (UID)
{user.id}
邮箱
{user.email || '未绑定'}
手机号码
{user.phone || '未设置'}
性别
{getGenderLabel(user.gender)}
个人网站
{user.website ? ( {user.website} ) : ( '未设置' )}
所在地
{user.location || '未设置'}
管理员身份
{user.is_admin ? ( 是 ) : ( 否 )}
个人简介
{user.bio || '这个人很懒,什么都没有留下。'}
{/* 下方左右并排布局容器 */}
{/* 修改密码面板 */}

修改密码

更改您的账号密码以确保安全。密码长度不能少于 8 位。

{user.need_change_password && (

密码风险提示

为了账号安全,您必须修改初始密码。

)}
setOldPassword(e.target.value)} className="h-8 text-xs rounded-lg" required />
setNewPassword(e.target.value)} className="h-8 text-xs rounded-lg" required />
setConfirmPassword(e.target.value)} className="h-8 text-xs rounded-lg" required />
{/* 账号绑定面板 */}

第三方账号绑定

管理并关联您的第三方授权账户,便于快捷登录与验证

{/* 已绑定账号列表 */}

已绑定账号

{externalAccountBindingsQuery.isPending ? (
) : (externalAccountBindingsQuery.data ?? []).length > 0 ? (
{(externalAccountBindingsQuery.data ?? []).map((binding) => (
{binding.auth_source_label} {binding.external_username || binding.email || "未提供账号标识"}
))}
) : (
暂无绑定的第三方账号
)}
{/* 绑定新账号列表 */}

绑定新账号

{publicAuthSourcesQuery.isPending ? (
) : (publicAuthSourcesQuery.data ?? []).length > 0 ? (
{(publicAuthSourcesQuery.data ?? []).map((source) => ( ))}
) : (
暂无可用第三方认证源
)}
{/* 编辑资料 Dialog */} 编辑个人资料 修改您的基本信息,保存后立即生效。
{/* 上传头像域 */}
fileInputRef.current?.click()} > {(nickname || "U").slice(0, 2).toUpperCase()}
点击上传/修改头像
setNickname(e.target.value)} className="h-8 text-xs rounded-lg" placeholder="请输入您的昵称" required />
setEmail(e.target.value)} className="h-8 text-xs rounded-lg" placeholder="请输入您的邮箱" />
setPhone(e.target.value)} className="h-8 text-xs rounded-lg" placeholder="请输入手机号" />
setWebsite(e.target.value)} className="h-8 text-xs rounded-lg" placeholder="https://..." />
setLocation(e.target.value)} className="h-8 text-xs rounded-lg" placeholder="如:北京" />