Spaces:
Running
Running
| 'use client'; | |
| /** | |
| * 登录/注册弹窗组件 | |
| */ | |
| import { useState } from 'react'; | |
| import { X, User, Lock, Eye, EyeOff } from 'lucide-react'; | |
| import { useAuthStore } from '@/store/authStore'; | |
| interface AuthModalProps { | |
| onClose: () => void; | |
| initialMode?: 'login' | 'register'; | |
| } | |
| export default function AuthModal({ onClose, initialMode = 'login' }: AuthModalProps) { | |
| const [mode, setMode] = useState<'login' | 'register'>(initialMode); | |
| const [username, setUsername] = useState(''); | |
| const [password, setPassword] = useState(''); | |
| const [confirmPassword, setConfirmPassword] = useState(''); | |
| const [showPassword, setShowPassword] = useState(false); | |
| const [error, setError] = useState(''); | |
| const { login, register, isLoading } = useAuthStore(); | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| setError(''); | |
| // 前端验证 | |
| if (username.trim().length < 3) { | |
| setError('用户名至少3个字符'); | |
| return; | |
| } | |
| if (password.length < 6) { | |
| setError('密码至少6位'); | |
| return; | |
| } | |
| if (mode === 'register' && password !== confirmPassword) { | |
| setError('两次密码输入不一致'); | |
| return; | |
| } | |
| try { | |
| if (mode === 'login') { | |
| await login(username.trim(), password); | |
| } else { | |
| await register(username.trim(), password); | |
| } | |
| onClose(); | |
| } catch (err: any) { | |
| setError(err.message || (mode === 'login' ? '登录失败' : '注册失败')); | |
| } | |
| }; | |
| const switchMode = () => { | |
| setMode(mode === 'login' ? 'register' : 'login'); | |
| setError(''); | |
| setConfirmPassword(''); | |
| }; | |
| return ( | |
| <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 p-4" onClick={onClose}> | |
| <div | |
| className="bg-[#1E212B] rounded-2xl w-full max-w-sm shadow-2xl border border-[#2A2D3C] overflow-hidden" | |
| onClick={e => e.stopPropagation()} | |
| > | |
| {/* Header */} | |
| <div className="px-5 py-4 border-b border-[#2A2D3C] flex items-center justify-between"> | |
| <h3 className="text-lg font-bold text-white"> | |
| {mode === 'login' ? '登录' : '注册'} | |
| </h3> | |
| <button | |
| onClick={onClose} | |
| className="w-8 h-8 flex items-center justify-center rounded-lg bg-[#2A2D3C] hover:bg-[#35394B] text-gray-400 hover:text-white transition-colors" | |
| > | |
| <X size={16} /> | |
| </button> | |
| </div> | |
| {/* Form */} | |
| <form onSubmit={handleSubmit} className="p-5 space-y-4"> | |
| {/* Error */} | |
| {error && ( | |
| <div className="bg-red-500/10 border border-red-500/30 text-red-400 text-sm px-4 py-2.5 rounded-lg"> | |
| {error} | |
| </div> | |
| )} | |
| {/* Username */} | |
| <div> | |
| <label className="text-gray-400 text-xs mb-1.5 block">用户名</label> | |
| <div className="relative"> | |
| <User size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" /> | |
| <input | |
| type="text" | |
| value={username} | |
| onChange={e => setUsername(e.target.value)} | |
| placeholder="请输入用户名(至少3位)" | |
| className="w-full bg-[#12141C] border border-gray-700/50 rounded-xl pl-10 pr-4 py-3 text-white text-sm placeholder-gray-600 focus:outline-none focus:border-blue-500 transition-colors" | |
| autoComplete="username" | |
| autoFocus | |
| /> | |
| </div> | |
| </div> | |
| {/* Password */} | |
| <div> | |
| <label className="text-gray-400 text-xs mb-1.5 block">密码</label> | |
| <div className="relative"> | |
| <Lock size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" /> | |
| <input | |
| type={showPassword ? 'text' : 'password'} | |
| value={password} | |
| onChange={e => setPassword(e.target.value)} | |
| placeholder="请输入密码(至少6位)" | |
| className="w-full bg-[#12141C] border border-gray-700/50 rounded-xl pl-10 pr-10 py-3 text-white text-sm placeholder-gray-600 focus:outline-none focus:border-blue-500 transition-colors" | |
| autoComplete={mode === 'login' ? 'current-password' : 'new-password'} | |
| /> | |
| <button | |
| type="button" | |
| onClick={() => setShowPassword(!showPassword)} | |
| className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-300" | |
| > | |
| {showPassword ? <EyeOff size={16} /> : <Eye size={16} />} | |
| </button> | |
| </div> | |
| </div> | |
| {/* Confirm Password (register only) */} | |
| {mode === 'register' && ( | |
| <div> | |
| <label className="text-gray-400 text-xs mb-1.5 block">确认密码</label> | |
| <div className="relative"> | |
| <Lock size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" /> | |
| <input | |
| type={showPassword ? 'text' : 'password'} | |
| value={confirmPassword} | |
| onChange={e => setConfirmPassword(e.target.value)} | |
| placeholder="请再次输入密码" | |
| className="w-full bg-[#12141C] border border-gray-700/50 rounded-xl pl-10 pr-4 py-3 text-white text-sm placeholder-gray-600 focus:outline-none focus:border-blue-500 transition-colors" | |
| autoComplete="new-password" | |
| /> | |
| </div> | |
| </div> | |
| )} | |
| {/* Submit */} | |
| <button | |
| type="submit" | |
| disabled={isLoading} | |
| className="w-full bg-gradient-to-r from-blue-600 to-blue-500 hover:from-blue-500 hover:to-blue-400 text-white font-bold py-3 rounded-xl transition-all duration-200 disabled:opacity-50 shadow-lg text-sm" | |
| > | |
| {isLoading ? '处理中...' : (mode === 'login' ? '登录' : '注册')} | |
| </button> | |
| {/* Switch mode */} | |
| <div className="text-center text-sm text-gray-500"> | |
| {mode === 'login' ? ( | |
| <>还没有账号?<button type="button" onClick={switchMode} className="text-blue-400 hover:text-blue-300 ml-1">立即注册</button></> | |
| ) : ( | |
| <>已有账号?<button type="button" onClick={switchMode} className="text-blue-400 hover:text-blue-300 ml-1">立即登录</button></> | |
| )} | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| ); | |
| } | |