new change
This commit is contained in:
@@ -1,12 +1,13 @@
|
|||||||
// src/components/auth/AuthForm.tsx
|
// src/components/auth/AuthForm.tsx
|
||||||
// 认证表单组件 - 包含登录和注册功能,同时提供测试账号信息
|
// 认证表单组件 - 包含登录和注册功能,同时提供测试账号信息
|
||||||
'use client';
|
'use client';
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { signIn } from 'next-auth/react';
|
import { signIn } from 'next-auth/react';
|
||||||
import { register } from '@/lib/api/actions';
|
import { register, getVerificationCode } from '@/lib/api/actions';
|
||||||
|
// 由于找不到 '@/components/ui/alert' 模块,推测可能路径有误,这里使用实际路径代替,需根据项目实际情况调整
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||||
import { Eye, EyeOff } from 'lucide-react';
|
import { Eye, EyeOff } from 'lucide-react';
|
||||||
|
|
||||||
@@ -23,6 +24,10 @@ export default function AuthForm({ type }: AuthFormProps) {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [success, setSuccess] = useState<string | null>(null);
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [verificationCode, setVerificationCode] = useState('');
|
||||||
|
const [isSendingCode, setIsSendingCode] = useState(false);
|
||||||
|
const [countdown, setCountdown] = useState(0);
|
||||||
|
const [codeSuccessMessage, setCodeSuccessMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -44,11 +49,22 @@ export default function AuthForm({ type }: AuthFormProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查测试账号情况
|
||||||
|
const isTestAccount = usernameField === 'test' && password === 'test';
|
||||||
|
|
||||||
|
// 对于非测试账号,验证验证码是否输入
|
||||||
|
if (!isTestAccount && !verificationCode) {
|
||||||
|
setError('请输入验证码');
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 使用next-auth的signIn功能进行登录,设置redirect为false以便处理错误
|
// 使用next-auth的signIn功能进行登录,设置redirect为false以便处理错误
|
||||||
const result = await signIn('credentials', {
|
const result = await signIn('credentials', {
|
||||||
username: usernameField, // 使用username或email作为用户名字段
|
username: usernameField, // 使用username或email作为用户名字段
|
||||||
email: email, // 传递email字段
|
email: email, // 传递email字段
|
||||||
password,
|
password,
|
||||||
|
verificationCode, // 传递验证码
|
||||||
redirect: false, // 不自动重定向,以便处理错误
|
redirect: false, // 不自动重定向,以便处理错误
|
||||||
callbackUrl: '/user-home' // 指定重定向目标
|
callbackUrl: '/user-home' // 指定重定向目标
|
||||||
});
|
});
|
||||||
@@ -70,12 +86,20 @@ export default function AuthForm({ type }: AuthFormProps) {
|
|||||||
setError('登录过程中发生未知错误');
|
setError('登录过程中发生未知错误');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 验证验证码是否输入
|
||||||
|
if (!verificationCode) {
|
||||||
|
setError('请输入验证码');
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 调用注册API
|
// 调用注册API
|
||||||
const result = await register({
|
const result = await register({
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
email,
|
email,
|
||||||
minecraftUsername
|
minecraftUsername,
|
||||||
|
verificationCode
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -100,8 +124,59 @@ export default function AuthForm({ type }: AuthFormProps) {
|
|||||||
const resetMessages = () => {
|
const resetMessages = () => {
|
||||||
setError(null);
|
setError(null);
|
||||||
setSuccess(null);
|
setSuccess(null);
|
||||||
|
setCodeSuccessMessage(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理获取验证码
|
||||||
|
const handleGetVerificationCode = async () => {
|
||||||
|
// 检查是否是测试环境使用测试账号
|
||||||
|
const isTestAccount = type === 'login' && username === 'test' && password === 'test';
|
||||||
|
if (isTestAccount) {
|
||||||
|
setCodeSuccessMessage('测试账号无需验证码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetEmail = type === 'login' ? (email || username) : email;
|
||||||
|
|
||||||
|
if (!targetEmail) {
|
||||||
|
setError(type === 'login' ? '请输入用户名/邮箱' : '请输入邮箱');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的邮箱格式验证
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (type === 'register' || (type === 'login' && targetEmail.includes('@'))) {
|
||||||
|
if (!emailRegex.test(targetEmail)) {
|
||||||
|
setError('请输入有效的邮箱地址');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSendingCode(true);
|
||||||
|
try {
|
||||||
|
const result = await getVerificationCode(targetEmail);
|
||||||
|
if (result.success) {
|
||||||
|
setCodeSuccessMessage('验证码已发送,请注意查收');
|
||||||
|
setCountdown(60); // 设置60秒倒计时
|
||||||
|
} else {
|
||||||
|
setError(result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取验证码失败:', error);
|
||||||
|
setError('获取验证码失败,请稍后再试');
|
||||||
|
} finally {
|
||||||
|
setIsSendingCode(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 倒计时效果
|
||||||
|
useEffect(() => {
|
||||||
|
if (countdown > 0) {
|
||||||
|
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [countdown]);
|
||||||
|
|
||||||
// 监听输入变化,清除错误消息
|
// 监听输入变化,清除错误消息
|
||||||
const handleInputChange = (setter: React.Dispatch<React.SetStateAction<string>>) => {
|
const handleInputChange = (setter: React.Dispatch<React.SetStateAction<string>>) => {
|
||||||
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
return (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -125,6 +200,14 @@ export default function AuthForm({ type }: AuthFormProps) {
|
|||||||
<AlertDescription>{success}</AlertDescription>
|
<AlertDescription>{success}</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 验证码发送成功消息 */}
|
||||||
|
{codeSuccessMessage && (
|
||||||
|
<Alert className="bg-blue-50 border-blue-200 text-blue-800">
|
||||||
|
<AlertTitle>提示</AlertTitle>
|
||||||
|
<AlertDescription>{codeSuccessMessage}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="username">用户名</Label>
|
<Label htmlFor="username">用户名</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -168,6 +251,33 @@ export default function AuthForm({ type }: AuthFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 验证码输入区域 */}
|
||||||
|
{type === 'login' && (username !== 'test' || password !== 'test') && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="verificationCode">验证码</Label>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<Input
|
||||||
|
id="verificationCode"
|
||||||
|
type="text"
|
||||||
|
value={verificationCode}
|
||||||
|
onChange={handleInputChange(setVerificationCode)}
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={handleGetVerificationCode}
|
||||||
|
disabled={isSendingCode || countdown > 0 || !email && !username}
|
||||||
|
className="whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{isSendingCode ? '发送中...' :
|
||||||
|
countdown > 0 ? `${countdown}s后重新发送` :
|
||||||
|
'获取验证码'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{type === 'register' && (
|
{type === 'register' && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
@@ -191,6 +301,31 @@ export default function AuthForm({ type }: AuthFormProps) {
|
|||||||
placeholder="您的Minecraft游戏内用户名"
|
placeholder="您的Minecraft游戏内用户名"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 注册表单的验证码输入区域 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="verificationCode">验证码</Label>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<Input
|
||||||
|
id="verificationCode"
|
||||||
|
type="text"
|
||||||
|
value={verificationCode}
|
||||||
|
onChange={handleInputChange(setVerificationCode)}
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={handleGetVerificationCode}
|
||||||
|
disabled={isSendingCode || countdown > 0 || !email}
|
||||||
|
className="whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{isSendingCode ? '发送中...' :
|
||||||
|
countdown > 0 ? `${countdown}s后重新发送` :
|
||||||
|
'获取验证码'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import axios from 'axios';
|
|||||||
|
|
||||||
// 配置axios实例,统一处理API请求
|
// 配置axios实例,统一处理API请求
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
|
//将baseURL改为实际服务器地址
|
||||||
baseURL: process.env.NEXT_PUBLIC_API_URL || '/api',
|
baseURL: process.env.NEXT_PUBLIC_API_URL || '/api',
|
||||||
timeout: 10000, // 设置10秒超时
|
timeout: 10000, // 设置10秒超时
|
||||||
headers: {
|
headers: {
|
||||||
@@ -95,7 +96,8 @@ export const serverSignOut = async () => {
|
|||||||
export const login = async (credentials: {
|
export const login = async (credentials: {
|
||||||
username?: string;
|
username?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
password: string
|
password: string;
|
||||||
|
verificationCode: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
// 对于测试环境,可以直接验证测试账号
|
// 对于测试环境,可以直接验证测试账号
|
||||||
@@ -129,6 +131,18 @@ export const login = async (credentials: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 实际环境中调用API
|
// 实际环境中调用API
|
||||||
|
// 验证码验证 - 测试环境下使用测试账号时可以跳过验证
|
||||||
|
if (!(process.env.NODE_ENV !== 'production' &&
|
||||||
|
usernameField === TEST_USERNAME &&
|
||||||
|
credentials.password === TEST_PASSWORD)) {
|
||||||
|
|
||||||
|
// 非测试账号需要验证验证码
|
||||||
|
const verifyResult = await verifyCode(usernameField, credentials.verificationCode);
|
||||||
|
if (!verifyResult.success) {
|
||||||
|
return verifyResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const response = await apiClient.post('/auth/login', credentials);
|
const response = await apiClient.post('/auth/login', credentials);
|
||||||
return { success: true, ...response.data };
|
return { success: true, ...response.data };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -146,12 +160,67 @@ export const login = async (credentials: {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 导出获取验证码函数
|
||||||
|
export const getVerificationCode = async (email: string) => {
|
||||||
|
try {
|
||||||
|
// 验证邮箱格式
|
||||||
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
if (!emailRegex.test(email)) {
|
||||||
|
return { success: false, error: '请输入有效的邮箱地址' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用API获取验证码
|
||||||
|
const response = await apiClient.post('/auth/get-verification-code', { email });
|
||||||
|
return { success: true, ...response.data };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取验证码失败:', error);
|
||||||
|
if (error instanceof axios.AxiosError) {
|
||||||
|
if (error.response?.status === 400) {
|
||||||
|
return { success: false, error: error.response.data?.error || '获取验证码失败,请检查邮箱格式' };
|
||||||
|
}
|
||||||
|
if (error.request) {
|
||||||
|
return { success: false, error: '网络连接失败,请检查您的网络设置' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { success: false, error: '获取验证码失败,请稍后再试' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出验证验证码函数
|
||||||
|
export const verifyCode = async (email: string, code: string) => {
|
||||||
|
try {
|
||||||
|
// 验证输入
|
||||||
|
if (!email?.trim()) {
|
||||||
|
return { success: false, error: '邮箱不能为空' };
|
||||||
|
}
|
||||||
|
if (!code?.trim()) {
|
||||||
|
return { success: false, error: '验证码不能为空' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用API验证验证码
|
||||||
|
const response = await apiClient.post('/auth/verify-code', { email, code });
|
||||||
|
return { success: true, ...response.data };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('验证码验证失败:', error);
|
||||||
|
if (error instanceof axios.AxiosError) {
|
||||||
|
if (error.response?.status === 400) {
|
||||||
|
return { success: false, error: error.response.data?.error || '验证码错误或已过期' };
|
||||||
|
}
|
||||||
|
if (error.request) {
|
||||||
|
return { success: false, error: '网络连接失败,请检查您的网络设置' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { success: false, error: '验证码验证失败,请稍后再试' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 导出注册函数
|
// 导出注册函数
|
||||||
export const register = async (userData: {
|
export const register = async (userData: {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
email: string;
|
email: string;
|
||||||
minecraftUsername: string;
|
minecraftUsername: string;
|
||||||
|
verificationCode: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
// 基本验证
|
// 基本验证
|
||||||
@@ -179,6 +248,12 @@ export const register = async (userData: {
|
|||||||
return { success: false, error: '请输入有效的邮箱地址' };
|
return { success: false, error: '请输入有效的邮箱地址' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证码验证
|
||||||
|
const verifyResult = await verifyCode(userData.email, userData.verificationCode);
|
||||||
|
if (!verifyResult.success) {
|
||||||
|
return verifyResult;
|
||||||
|
}
|
||||||
|
|
||||||
// 实际环境中调用API
|
// 实际环境中调用API
|
||||||
const response = await apiClient.post('/auth/register', userData);
|
const response = await apiClient.post('/auth/register', userData);
|
||||||
return { success: true, ...response.data };
|
return { success: true, ...response.data };
|
||||||
|
|||||||
Reference in New Issue
Block a user