213 lines
7.4 KiB
TypeScript
213 lines
7.4 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { X, User, Lock, Mail, Eye, EyeOff } from 'lucide-react';
|
|
import { login, register } from '../utils/api';
|
|
import { auth } from '../utils/auth';
|
|
import { useToast } from '../contexts/ToastContext';
|
|
|
|
interface AuthModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onLoginSuccess: (user: any) => void;
|
|
}
|
|
|
|
export function AuthModal({ isOpen, onClose, onLoginSuccess }: AuthModalProps) {
|
|
const [isLogin, setIsLogin] = useState(true);
|
|
const [loading, setLoading] = useState(false);
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const toast = useToast();
|
|
|
|
const [formData, setFormData] = useState({
|
|
username: '',
|
|
password: '',
|
|
email: '',
|
|
fullName: ''
|
|
});
|
|
|
|
// Reset form when modal opens
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
setFormData({
|
|
username: '',
|
|
password: '',
|
|
email: '',
|
|
fullName: ''
|
|
});
|
|
setIsLogin(true);
|
|
setShowPassword(false);
|
|
}
|
|
}, [isOpen]);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setLoading(true);
|
|
|
|
try {
|
|
let data;
|
|
if (isLogin) {
|
|
data = await login(formData.username, formData.password);
|
|
} else {
|
|
data = await register(
|
|
formData.username,
|
|
formData.password,
|
|
formData.email || undefined,
|
|
formData.fullName || undefined
|
|
);
|
|
}
|
|
|
|
// Store token and user info
|
|
auth.setToken(data.access_token);
|
|
auth.setUser(data.user);
|
|
|
|
toast.success(isLogin ? '登录成功!' : '注册成功!正在为您登录...');
|
|
|
|
onLoginSuccess(data.user);
|
|
onClose();
|
|
|
|
} catch (err: any) {
|
|
console.error('Auth error:', err);
|
|
const msg = err.response?.data?.detail || '操作失败,请重试';
|
|
const errorMsg = typeof msg === 'string' ? msg : JSON.stringify(msg);
|
|
toast.error(errorMsg);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setFormData({
|
|
...formData,
|
|
[e.target.name]: e.target.value
|
|
});
|
|
};
|
|
|
|
const toggleMode = () => {
|
|
setIsLogin(!isLogin);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/90 backdrop-blur-xl p-4"
|
|
onClick={onClose} // Close modal when clicking outside
|
|
>
|
|
<div
|
|
className="bg-gray-900/90 border border-white/10 rounded-2xl w-full max-w-md shadow-2xl overflow-hidden animate-in fade-in zoom-in-95 duration-200"
|
|
onClick={(e) => e.stopPropagation()} // Prevent closing when clicking inside
|
|
>
|
|
{/* Header */}
|
|
<div className="relative p-6 pb-4 border-b border-white/10">
|
|
<button
|
|
onClick={onClose}
|
|
className="absolute top-4 right-4 text-gray-400 hover:text-white transition-colors"
|
|
>
|
|
<X size={20} />
|
|
</button>
|
|
<h2 className="text-2xl font-bold text-white text-center">
|
|
{isLogin ? '欢迎回来' : '创建账号'}
|
|
</h2>
|
|
<p className="text-gray-400 text-sm text-center mt-1">
|
|
Cosmo - Deep Space Explorer
|
|
</p>
|
|
</div>
|
|
|
|
{/* Form */}
|
|
<form onSubmit={handleSubmit} className="p-6 space-y-4">
|
|
<div className="space-y-1">
|
|
<label className="text-xs text-gray-400 ml-1">用户名</label>
|
|
<div className="relative">
|
|
<User size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" />
|
|
<input
|
|
name="username"
|
|
type="text"
|
|
required
|
|
value={formData.username}
|
|
onChange={handleChange}
|
|
placeholder="Username"
|
|
className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-4 py-2.5 text-white focus:outline-none focus:border-blue-500 transition-colors"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{!isLogin && (
|
|
<>
|
|
<div className="space-y-1">
|
|
<label className="text-xs text-gray-400 ml-1">邮箱 (可选)</label>
|
|
<div className="relative">
|
|
<Mail size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" />
|
|
<input
|
|
name="email"
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={handleChange}
|
|
placeholder="Email"
|
|
className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-4 py-2.5 text-white focus:outline-none focus:border-blue-500 transition-colors"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="text-xs text-gray-400 ml-1">昵称 (可选)</label>
|
|
<div className="relative">
|
|
<User size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" />
|
|
<input
|
|
name="fullName"
|
|
type="text"
|
|
value={formData.fullName}
|
|
onChange={handleChange}
|
|
placeholder="Full Name"
|
|
className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-4 py-2.5 text-white focus:outline-none focus:border-blue-500 transition-colors"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
<div className="space-y-1">
|
|
<label className="text-xs text-gray-400 ml-1">密码</label>
|
|
<div className="relative">
|
|
<Lock size={18} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" />
|
|
<input
|
|
name="password"
|
|
type={showPassword ? "text" : "password"}
|
|
required
|
|
value={formData.password}
|
|
onChange={handleChange}
|
|
placeholder="Password"
|
|
className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-10 py-2.5 text-white focus:outline-none focus:border-blue-500 transition-colors"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-white transition-colors"
|
|
>
|
|
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
className="w-full py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-all shadow-lg shadow-blue-600/20 disabled:opacity-50 disabled:cursor-not-allowed mt-2"
|
|
>
|
|
{loading ? '处理中...' : (isLogin ? '登录' : '注册并登录')}
|
|
</button>
|
|
</form>
|
|
|
|
{/* Footer */}
|
|
<div className="p-4 bg-white/5 border-t border-white/5 text-center">
|
|
<p className="text-sm text-gray-400">
|
|
{isLogin ? '还没有账号?' : '已有账号?'}
|
|
<button
|
|
onClick={toggleMode}
|
|
className="ml-2 text-blue-400 hover:text-blue-300 font-medium transition-colors"
|
|
>
|
|
{isLogin ? '立即注册' : '去登录'}
|
|
</button>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|