"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { motion, AnimatePresence } from "framer-motion"; import { ArrowLeft, Mail, Loader2, LogOut, Lock, Edit3, Check, X, Eye, EyeOff, Zap, Trophy, ChevronRight, } from "lucide-react"; import Card from "@/components/Card"; import Input from "@/components/Input"; import ProfileHistoryCard from "@/components/ProfileHistoryCard"; import ProfileFavoritesCard from "@/components/ProfileFavoritesCard"; import { useToast } from "@/hooks/useToast"; import { ProfileCardSkeleton, RecordItemSkeleton } from "@/components/Skeleton"; import { getUserId, getCachedProfile, setCachedProfile, setCachedPreferences, logout } from "@/lib/userId"; import { getAvatarBg, AVATARS } from "@/lib/avatars"; import type { UserProfile, UserPreferences, DecisionRecord, FavoriteRecord } from "@/types"; export default function ProfilePage() { const router = useRouter(); const [userId, setUserId] = useState(""); const [profile, setProfile] = useState<(UserProfile & { email?: string; preferences?: UserPreferences; decisionCount?: number }) | null>(null); const [loading, setLoading] = useState(true); const [history, setHistory] = useState([]); const [favorites, setFavorites] = useState([]); const [historyLoading, setHistoryLoading] = useState(false); const [favLoading, setFavLoading] = useState(false); const [editingUsername, setEditingUsername] = useState(false); const [newUsername, setNewUsername] = useState(""); const [usernameSaving, setUsernameSaving] = useState(false); const [usernameMsg, setUsernameMsg] = useState(""); const [editingPassword, setEditingPassword] = useState(false); const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [passwordSaving, setPasswordSaving] = useState(false); const [passwordMsg, setPasswordMsg] = useState(""); const [editingAvatar, setEditingAvatar] = useState(false); const [email, setEmail] = useState(""); const [emailSaving, setEmailSaving] = useState(false); const [emailMsg, setEmailMsg] = useState(""); const [showHistory, setShowHistory] = useState(true); const [showFavorites, setShowFavorites] = useState(true); const toast = useToast(); useEffect(() => { const cached = getCachedProfile(); if (!cached) { router.push("/"); return; } const id = getUserId(); setUserId(id); fetch(`/api/user?id=${id}`) .then((r) => r.json()) .then((data) => { if (data) { setProfile(data); setEmail(data.email ?? ""); setCachedProfile({ id: data.id, username: data.username, avatar: data.avatar }); if (data.preferences) setCachedPreferences(data.preferences); } else { router.push("/"); } }) .catch(() => { setProfile({ ...cached }); }) .finally(() => setLoading(false)); }, [router]); useEffect(() => { if (!userId) return; setHistoryLoading(true); fetch(`/api/user/history?userId=${userId}`) .then((r) => { if (!r.ok) throw new Error(); return r.json(); }) .then((data) => setHistory(Array.isArray(data) ? data : [])) .catch(() => {}) .finally(() => setHistoryLoading(false)); setFavLoading(true); fetch(`/api/user/favorite?userId=${userId}`) .then((r) => { if (!r.ok) throw new Error(); return r.json(); }) .then((data) => setFavorites(Array.isArray(data) ? data : [])) .catch(() => {}) .finally(() => setFavLoading(false)); }, [userId]); const handleSaveUsername = async () => { const trimmed = newUsername.trim(); if (trimmed.length < 2 || trimmed.length > 16) { setUsernameMsg("用户名需要 2-16 个字符"); return; } setUsernameSaving(true); setUsernameMsg(""); try { const res = await fetch("/api/user", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId, username: trimmed }), }); const data = await res.json(); if (res.ok) { setProfile((prev) => prev ? { ...prev, username: trimmed } : prev); setCachedProfile({ id: userId, username: trimmed, avatar: profile!.avatar }); setEditingUsername(false); toast.show("用户名已更新"); } else { setUsernameMsg(data.error ?? "更新失败"); } } catch { setUsernameMsg("网络错误"); } finally { setUsernameSaving(false); } }; const handleSavePassword = async () => { if (!currentPassword) { setPasswordMsg("请输入当前密码"); return; } if (newPassword.length < 6) { setPasswordMsg("新密码至少 6 个字符"); return; } if (newPassword !== confirmPassword) { setPasswordMsg("两次密码不一致"); return; } setPasswordSaving(true); setPasswordMsg(""); try { const res = await fetch("/api/user", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId, currentPassword, newPassword }), }); const data = await res.json(); if (res.ok) { setEditingPassword(false); setCurrentPassword(""); setNewPassword(""); setConfirmPassword(""); toast.show("密码已更新"); } else { setPasswordMsg(data.error ?? "更新失败"); } } catch { setPasswordMsg("网络错误"); } finally { setPasswordSaving(false); } }; const handleSaveAvatar = async (emoji: string) => { try { const res = await fetch("/api/user", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId, avatar: emoji }), }); if (res.ok) { setProfile((prev) => prev ? { ...prev, avatar: emoji } : prev); setCachedProfile({ id: userId, username: profile!.username, avatar: emoji }); setEditingAvatar(false); toast.show("头像已更新"); } } catch { toast.show("更新失败"); } }; const handleSaveEmail = async () => { if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { setEmailMsg("邮箱格式不正确"); return; } setEmailSaving(true); setEmailMsg(""); try { const res = await fetch("/api/user", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId, email: email || null }), }); if (res.ok) { setEmailMsg(email ? "邮箱已绑定" : "邮箱已解绑"); } else { const data = await res.json().catch(() => ({})); setEmailMsg(data.error ?? "保存失败"); } } catch { setEmailMsg("网络错误"); } finally { setEmailSaving(false); } }; const handleRemoveFavorite = async (favId: string) => { try { await fetch("/api/user/favorite", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId, favoriteId: favId }), }); setFavorites((f) => f.filter((x) => x.id !== favId)); toast.show("已取消收藏"); } catch { toast.show("操作失败"); } }; const handleLogout = () => { logout(); router.push("/"); }; if (loading) { return (
); } if (!profile) return null; return (
{/* Profile card */}
{editingUsername ? (
{ setNewUsername(e.target.value.slice(0, 16)); setUsernameMsg(""); }} maxLength={16} autoFocus size="sm" className="flex-1" />
) : (

{profile.username}

)} {usernameMsg &&

{usernameMsg}

} {(profile.decisionCount ?? 0) > 0 && (

已拯救 {profile.decisionCount} 次选择困难症

)}
{/* Avatar picker */} {editingAvatar && (
{AVATARS.map((a) => ( ))}
)}
{/* Change password */} {editingPassword && (

当前密码

{ setCurrentPassword(e.target.value); setPasswordMsg(""); }} className="pr-9" />

新密码

{ setNewPassword(e.target.value); setPasswordMsg(""); }} placeholder="至少 6 个字符" className="mt-1" />

确认新密码

{ setConfirmPassword(e.target.value); setPasswordMsg(""); }} placeholder="再次输入新密码" className="mt-1" />
{passwordMsg && (

{passwordMsg}

)}
)}
{/* Email binding */}

绑定邮箱

(可选)
{ setEmail(e.target.value); setEmailMsg(""); }} className="flex-1" />
{emailMsg && (

{emailMsg}

)}
{/* Achievements link */} setShowHistory((v) => !v)} onEmpty={() => router.push("/blindbox")} delay={0.2} /> setShowFavorites((v) => !v)} onRemove={handleRemoveFavorite} onEmpty={() => router.push("/blindbox")} delay={0.2} /> {/* Logout */}
); }