feat: 创建登录/注册页面,支持用户认证和注册功能。
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import authService from '../../services/authService';
|
||||
import './Login.css';
|
||||
|
||||
interface LoginProps {
|
||||
mode?: 'login' | 'register';
|
||||
}
|
||||
|
||||
export default function Login({ mode = 'login' }: LoginProps) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [isLogin, setIsLogin] = useState(mode === 'login');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
// Shared form data or separate? Let's use shared for email/password continuity
|
||||
const [formData, setFormData] = useState({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
// Redirect if authenticated
|
||||
useEffect(() => {
|
||||
if (authService.isAuthenticated()) {
|
||||
const from = (location.state as { from?: { pathname: string } })?.from?.pathname || '/home';
|
||||
navigate(from, { replace: true });
|
||||
}
|
||||
}, [navigate, location]);
|
||||
|
||||
// Sync prop mode
|
||||
useEffect(() => {
|
||||
setIsLogin(mode === 'login');
|
||||
}, [mode]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||
setError('');
|
||||
};
|
||||
|
||||
const handleRegister = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
await authService.register({
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
username: formData.username,
|
||||
});
|
||||
navigate('/home');
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : '注册失败,请重试';
|
||||
setError(errorMessage);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
await authService.login({
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
});
|
||||
navigate('/home');
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : '登录失败,请重试';
|
||||
setError(errorMessage);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const Particles = () => {
|
||||
return (
|
||||
<div className="particles-container" style={{ position: 'absolute', inset: 0, overflow: 'hidden', pointerEvents: 'none', zIndex: 0 }}>
|
||||
{[...Array(15)].map((_, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
initial={{
|
||||
x: Math.random() * window.innerWidth,
|
||||
y: Math.random() * window.innerHeight,
|
||||
opacity: Math.random() * 0.5 + 0.1,
|
||||
}}
|
||||
animate={{
|
||||
y: [null, Math.random() * window.innerHeight],
|
||||
x: [null, Math.random() * window.innerWidth],
|
||||
}}
|
||||
transition={{
|
||||
duration: Math.random() * 20 + 20,
|
||||
repeat: Infinity,
|
||||
repeatType: "reverse",
|
||||
ease: "linear",
|
||||
}}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: Math.random() * 4 + 2 + 'px',
|
||||
height: Math.random() * 4 + 2 + 'px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: i % 2 === 0 ? '#6366f1' : '#ec4899',
|
||||
filter: 'blur(1px)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="login-page">
|
||||
<Particles />
|
||||
|
||||
<div className={`container ${!isLogin ? 'right-panel-active' : ''}`} id="container">
|
||||
{/* Sign Up Form */}
|
||||
<div className="form-container sign-up-container">
|
||||
<form onSubmit={handleRegister}>
|
||||
<h1>创建账户</h1>
|
||||
<div className="social-container">
|
||||
<a href={authService.getGitHubLoginUrl()} title="Github Login">
|
||||
<Icon icon="mdi:github" width="20" />
|
||||
</a>
|
||||
<a href={authService.getGiteeLoginUrl()} title="Gitee Login">
|
||||
<Icon icon="simple-icons:gitee" width="20" />
|
||||
</a>
|
||||
</div>
|
||||
<span>或使用邮箱注册</span>
|
||||
|
||||
<div className="login-field">
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
className="login-input"
|
||||
placeholder="用户名"
|
||||
value={formData.username}
|
||||
onChange={handleChange}
|
||||
required={!isLogin}
|
||||
/>
|
||||
</div>
|
||||
<div className="login-field">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
className="login-input"
|
||||
placeholder="邮箱"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="login-field">
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
className="login-input"
|
||||
placeholder="密码"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
minLength={8}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && !isLogin && <p style={{ color: 'red', margin: '5px 0' }}>{error}</p>}
|
||||
|
||||
<button className="login-button" disabled={loading}>
|
||||
{loading ? <Icon icon="line-md:loading-twotone-loop" /> : '注册'}
|
||||
</button>
|
||||
|
||||
<div className="mobile-toggle">
|
||||
<span onClick={() => setIsLogin(true)}>已有账户?去登录</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Sign In Form */}
|
||||
<div className="form-container sign-in-container">
|
||||
<form onSubmit={handleLogin}>
|
||||
<h1>欢迎回来</h1>
|
||||
<div className="social-container">
|
||||
<a href={authService.getGitHubLoginUrl()} title="Github Login">
|
||||
<Icon icon="mdi:github" width="20" />
|
||||
</a>
|
||||
<a href={authService.getGiteeLoginUrl()} title="Gitee Login">
|
||||
<Icon icon="simple-icons:gitee" width="20" />
|
||||
</a>
|
||||
</div>
|
||||
<span>或使用账户登录</span>
|
||||
|
||||
<div className="login-field">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
className="login-input"
|
||||
placeholder="邮箱"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="login-field">
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
className="login-input"
|
||||
placeholder="密码"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p style={{ margin: '10px 0', fontSize: '12px', cursor: 'pointer' }}>忘记密码?</p>
|
||||
|
||||
{error && isLogin && <p style={{ color: 'red', margin: '5px 0' }}>{error}</p>}
|
||||
|
||||
<button className="login-button" disabled={loading}>
|
||||
{loading ? <Icon icon="line-md:loading-twotone-loop" /> : '登录'}
|
||||
</button>
|
||||
|
||||
<div className="mobile-toggle">
|
||||
<span onClick={() => setIsLogin(false)}>还没有账户?去注册</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Overlay Container */}
|
||||
<div className="overlay-container">
|
||||
<div className="overlay">
|
||||
<div className="overlay-panel overlay-left">
|
||||
<h1>欢迎回来!</h1>
|
||||
<p>为了保持与我们的联系,请登录您的个人信息</p>
|
||||
<button className="ghost" onClick={() => setIsLogin(true)}>
|
||||
去登录
|
||||
</button>
|
||||
</div>
|
||||
<div className="overlay-panel overlay-right">
|
||||
<h1>你好,朋友!</h1>
|
||||
<p>输入您的个人详细信息并开始与我们一起管理旅程</p>
|
||||
<button className="ghost" onClick={() => setIsLogin(false)}>
|
||||
去注册
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user