"use client"; import * as React from "react"; import { useState, useRef, useEffect } from "react"; import { useUser } from "../user-context"; import { useRouter } from "next/navigation"; import { cn } from "@/lib/utils"; import { Settings, User, Bell, Shield, Database, Mail, Globe, Palette, Monitor, Moon, Sun, Check, ChevronDown, Search, X, Save, RotateCcw } from "lucide-react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Badge } from "@/components/ui/badge"; import { Textarea } from "@/components/ui/textarea"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; interface Tag { id: string; label: string; color?: string; } interface TagInputProps { onChange?: (tags: Array) => void; defaultTags?: Array; suggestions?: Array; maxTags?: number; label?: string; placeholder?: string; error?: string; } function useTags({ onChange, defaultTags = [], maxTags = 10, }: { onChange?: (tags: Tag[]) => void; defaultTags?: Tag[]; maxTags?: number; }) { const [tags, setTags] = useState(defaultTags); function addTag(tag: Tag) { if (tags.length >= maxTags) return; const newTags = [...tags, tag]; setTags(newTags); onChange?.(newTags); return newTags; } function removeTag(tagId: string) { const newTags = tags.filter((t) => t.id !== tagId); setTags(newTags); onChange?.(newTags); return newTags; } function removeLastTag() { if (tags.length === 0) return; return removeTag(tags[tags.length - 1].id); } return { tags, setTags, addTag, removeTag, removeLastTag, hasReachedMax: tags.length >= maxTags, }; } function useClickOutside( ref: React.RefObject, handler: (event: MouseEvent | TouchEvent) => void, mouseEvent: 'mousedown' | 'mouseup' = 'mousedown' ): void { useEffect(() => { const listener = (event: MouseEvent | TouchEvent) => { const el = ref?.current; const target = event.target; if (!el || !target || el.contains(target as Node)) { return; } handler(event); }; document.addEventListener(mouseEvent, listener); document.addEventListener('touchstart', listener); return () => { document.removeEventListener(mouseEvent, listener); document.removeEventListener('touchstart', listener); }; }, [ref, handler, mouseEvent]); } const tagStyles = { base: "inline-flex items-center gap-1.5 px-2 py-0.5 text-sm rounded-md transition-colors duration-150", colors: { blue: "bg-blue-50 text-blue-700 border border-blue-200 hover:border-blue-300 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-700/30 dark:hover:border-blue-600/50", purple: "bg-purple-50 text-purple-700 border border-purple-200 hover:border-purple-300 dark:bg-purple-900/30 dark:text-purple-300 dark:border-purple-700/30 dark:hover:border-purple-600/50", green: "bg-green-50 text-green-700 border border-green-200 hover:border-green-300 dark:bg-green-900/30 dark:text-green-300 dark:border-green-700/30 dark:hover:border-green-600/50", }, }; function TagInput({ onChange, defaultTags = [], suggestions = [ { id: "nextjs", label: "Next.js" }, { id: "react", label: "React" }, { id: "tailwind", label: "Tailwind" }, ], maxTags = 10, label = "Tags", placeholder = "Add tags...", error, }: TagInputProps) { const { tags, addTag, removeTag, removeLastTag } = useTags({ onChange, defaultTags, maxTags, }); const [input, setInput] = useState(""); const [isOpen, setIsOpen] = useState(false); const [selectedIndex, setSelectedIndex] = useState(0); const inputRef = useRef(null); const containerRef = useRef(null); const filteredSuggestions = suggestions .filter( (suggestion: Tag) => typeof suggestion.label === "string" && typeof input === "string" && suggestion.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 && !tags.some((tag: Tag) => tag.id === suggestion.id) ) .slice(0, 5); const canAddNewTag = input.length > 0 && !suggestions.some((s) => s.label.toLowerCase() === input.toLowerCase()); function handleKeyDown(e: React.KeyboardEvent) { if (e.key === "Backspace" && input === "" && tags.length > 0) { removeLastTag(); } else if (e.key === "Enter" && input) { e.preventDefault(); if (isOpen && filteredSuggestions[selectedIndex]) { addTag(filteredSuggestions[selectedIndex]); setInput(""); setIsOpen(false); } else if (canAddNewTag) { addTag({ id: input, label: input }); setInput(""); setIsOpen(false); } } else if (e.key === "Escape") { setIsOpen(false); } } useClickOutside(containerRef as React.RefObject, () => setIsOpen(false)); return (
{label && ( )}
{tags.map((tag) => ( {tag.label} ))} { setInput(e.target.value); setIsOpen(true); setSelectedIndex(0); }} onFocus={() => setIsOpen(true)} onKeyDown={handleKeyDown} placeholder={tags.length === 0 ? placeholder : ""} className="flex-1 min-w-[120px] bg-transparent h-7 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none" /> {isOpen && (input || filteredSuggestions.length > 0) && (
选择标签或创建新标签
{filteredSuggestions.map((suggestion, index) => ( ))} {canAddNewTag && ( )}
)}
{error && (

{error}

)}
); } interface SettingsSectionProps { icon: React.ReactNode; title: string; description: string; children: React.ReactNode; } function SettingsSection({ icon, title, description, children }: SettingsSectionProps) { return ( {icon} {title} {description} {children} ); } interface SettingItemProps { label: string; description?: string; children: React.ReactNode; badge?: string; } function SettingItem({ label, description, children, badge }: SettingItemProps) { return (
{badge && {badge}}
{description && (

{description}

)}
{children}
); } export default function MePage() { const { isLoading, user } = useUser(); const router = useRouter(); const [settings, setSettings] = useState({ emailNotifications: true, pushNotifications: false, marketingEmails: true, twoFactorAuth: false, sessionTimeout: "30", theme: "system", language: "zh", timezone: "Asia/Shanghai", autoSave: true, dataRetention: "90", apiAccess: false, debugMode: false, }); const [profile, setProfile] = useState({ name: user?.name || "", email: user?.email || "", role: "用户", department: "IT", bio: "热爱技术的开发者,专注于创造优秀的用户体验。", }); const [tags, setTags] = useState([ { id: "frontend", label: "前端开发", color: tagStyles.colors.blue }, { id: "react", label: "React", color: tagStyles.colors.green }, ]); // 认证检查已在 layout.tsx 中处理,无需在此重复检查 useEffect(() => { if (user) { setProfile(prev => ({ ...prev, name: user.name || "", email: user.email || "", })); } }, [user]); const handleSettingChange = (key: string, value: any) => { setSettings(prev => ({ ...prev, [key]: value })); }; const handleProfileChange = (key: string, value: string) => { setProfile(prev => ({ ...prev, [key]: value })); }; const handleSave = () => { // console.log("Settings saved:", settings); // console.log("Profile saved:", profile); // console.log("Tags saved:", tags); // TODO: 实际保存到后端 }; const handleReset = () => { setSettings({ emailNotifications: true, pushNotifications: false, marketingEmails: true, twoFactorAuth: false, sessionTimeout: "30", theme: "system", language: "zh", timezone: "Asia/Shanghai", autoSave: true, dataRetention: "90", apiAccess: false, debugMode: false, }); }; if (isLoading) { return (
); } return (
{/* Header */}

个人设置

管理您的个人资料、偏好设置和账户安全

{/* Profile Section */}
} title="个人资料" description="管理您的个人信息和账户设置" >
{profile.name.split(' ').map(n => n[0] || '用').join('').slice(0, 2)}

{profile.name || '用户'}

{profile.role}

{profile.department}
handleProfileChange('name', e.target.value)} className="mt-1" />
handleProfileChange('email', e.target.value)} className="mt-1" />