import { useState, useRef, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { ArrowLeft, LayoutDashboard, Settings } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { Spinner } from '@/components/ui/spinner'; import { notify } from '@/lib/notify'; import { useAuth } from '../hooks/useAuth'; import { api } from '../api/client'; import { useForumLimits } from '../hooks/useForumLimits'; const nickSchema = z.object({ nickname: z.string().min(1, '请输入昵称').max(64), }); const pwdSchema = (minLen: number) => z.object({ old_password: z.string().min(1, '请输入当前密码'), new_password: z.string().min(minLen, `新密码至少 ${minLen} 位`), confirm_password: z.string().min(1, '请确认新密码'), }).refine(d => d.new_password === d.confirm_password, { message: '两次输入的新密码不一致', path: ['confirm_password'], }); type NickValues = z.infer; type PwdValues = z.infer>; export default function ProfilePage() { const nav = useNavigate(); const { user, loading: authLoading, refresh } = useAuth(); const [nickLoading, setNickLoading] = useState(false); const [pwdLoading, setPwdLoading] = useState(false); const [avatarLoading, setAvatarLoading] = useState(false); const fileRef = useRef(null); const { limits } = useForumLimits(); const nickForm = useForm({ resolver: zodResolver(nickSchema), values: { nickname: user?.nickname ?? '' }, }); const pwdForm = useForm({ resolver: zodResolver(pwdSchema(limits.password_min_len)), defaultValues: { old_password: '', new_password: '', confirm_password: '' }, }); useEffect(() => { if (!authLoading && !user) { nav('/login'); } }, [authLoading, user, nav]); if (authLoading) { return
; } if (!user) return null; const onUpdateNick = async (values: NickValues) => { setNickLoading(true); try { await api.updateNickname(values.nickname); await refresh(); notify.success('昵称已更新'); } catch (e: unknown) { notify.error(e instanceof Error ? e.message : '更新失败'); } finally { setNickLoading(false); } }; const onUpdatePwd = async (values: PwdValues) => { setPwdLoading(true); try { await api.updatePassword(values.old_password, values.new_password); notify.success('密码已修改,请重新登录'); pwdForm.reset(); await api.logout(); nav('/login'); } catch (e: unknown) { notify.error(e instanceof Error ? e.message : '修改失败'); } finally { setPwdLoading(false); } }; const onAvatarChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (file.size > limits.avatar_max_mb * 1024 * 1024) { notify.error(`头像不能超过 ${limits.avatar_max_mb}MB`); return; } setAvatarLoading(true); try { await api.uploadAvatar(file); await refresh(); notify.success('头像已更新'); } catch (err: unknown) { notify.error(err instanceof Error ? err.message : '上传失败'); } finally { setAvatarLoading(false); if (fileRef.current) fileRef.current.value = ''; } }; return (
{user.avatar ? : user.nickname[0]}

{user.nickname}

@{user.username}
{user.role === 'admin' && 管理员}
{user.role === 'admin' && (
管理员入口

管理板块、用户、帖子及系统设置

)}
基本资料
用户名 ( 昵称 )} />
头像

支持 JPG、PNG、GIF、WebP,不超过 2MB

修改密码
( 当前密码 )} /> ( 新密码 )} /> ( 确认新密码 )} />
); }