diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts new file mode 100644 index 0000000..08619c3 --- /dev/null +++ b/src/app/api/auth/login/route.ts @@ -0,0 +1,50 @@ +// 登录API端点 +import { login } from '@/lib/api/actions'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function POST(request: NextRequest) { + try { + // 验证请求方法 + if (request.method !== 'POST') { + return NextResponse.json({ success: false, error: '不支持的请求方法' }, { status: 405 }); + } + + // 验证请求体 + let credentials; + try { + credentials = await request.json(); + } catch (jsonError) { + return NextResponse.json({ success: false, error: '请求体格式错误' }, { status: 400 }); + } + + // 基本参数验证 + if (!credentials || typeof credentials !== 'object') { + return NextResponse.json({ success: false, error: '请求参数无效' }, { status: 400 }); + } + + // 调用登录函数 + const result = await login(credentials); + + // 根据结果返回响应 + if (result.success) { + // 设置CORS头 + const response = NextResponse.json({ success: true, user: result.user }, { status: 200 }); + response.headers.set('Access-Control-Allow-Origin', '*'); + response.headers.set('Access-Control-Allow-Methods', 'POST'); + response.headers.set('Access-Control-Allow-Headers', 'Content-Type'); + + return response; + } else { + // 根据不同错误类型返回不同的状态码 + const statusCode = result.error.includes('不存在') || result.error.includes('错误') ? 401 : 400; + return NextResponse.json({ success: false, error: result.error }, { status: statusCode }); + } + } catch (error) { + console.error('Login API error:', error); + // 对于不同类型的错误返回具体错误信息 + if (error instanceof Error) { + return NextResponse.json({ success: false, error: error.message || '服务器内部错误' }, { status: 500 }); + } + return NextResponse.json({ success: false, error: '服务器内部错误' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts new file mode 100644 index 0000000..9878a35 --- /dev/null +++ b/src/app/api/auth/logout/route.ts @@ -0,0 +1,69 @@ +// src/app/api/auth/logout/route.ts +import { NextResponse } from 'next/server'; +import { cookies } from 'next/headers'; + +// 退出登录API端点 +export async function POST() { + try { + // 获取cookie存储 + const cookieStore = cookies(); + + // 清除所有与next-auth相关的cookie + const allCookies = cookieStore.getAll(); + allCookies.forEach(cookie => { + if (cookie.name.includes('next-auth')) { + cookieStore.delete(cookie.name); + } + }); + + // 返回成功响应 + return NextResponse.json({ + success: true, + message: '退出登录成功' + }, { status: 200 }); + } catch (error) { + console.error('退出登录API错误:', error); + // 对于不同类型的错误返回具体错误信息 + if (error instanceof Error) { + return NextResponse.json({ + success: false, + error: error.message || '服务器内部错误' + }, { status: 500 }); + } + return NextResponse.json({ + success: false, + error: '服务器内部错误' + }, { status: 500 }); + } +} + +// 处理GET请求,重定向到登录页面 +export async function GET() { + try { + // 创建重定向响应到登录页面 + const response = NextResponse.redirect(new URL('/login', process.env.NEXTAUTH_URL || 'http://localhost:3000')); + + // 清除所有与next-auth相关的cookie + const cookieStore = cookies(); + const allCookies = cookieStore.getAll(); + allCookies.forEach(cookie => { + if (cookie.name.includes('next-auth')) { + cookieStore.delete(cookie.name); + } + }); + + return response; + } catch (error) { + console.error('退出登录重定向错误:', error); + // 发生错误时,仍然尝试重定向到登录页面 + try { + return NextResponse.redirect(new URL('/login', process.env.NEXTAUTH_URL || 'http://localhost:3000')); + } catch (redirectError) { + // 如果重定向也失败,返回错误信息 + return NextResponse.json({ + success: false, + error: '退出登录失败,请刷新页面重试' + }, { status: 500 }); + } + } +} \ No newline at end of file diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts new file mode 100644 index 0000000..011545c --- /dev/null +++ b/src/app/api/auth/register/route.ts @@ -0,0 +1,67 @@ +// 注册API端点 +import { register } from '@/lib/api/actions'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function POST(request: NextRequest) { + try { + // 验证请求方法 + if (request.method !== 'POST') { + return NextResponse.json({ success: false, error: '不支持的请求方法' }, { status: 405 }); + } + + // 验证请求体 + let userData; + try { + userData = await request.json(); + } catch (jsonError) { + return NextResponse.json({ success: false, error: '请求体格式错误' }, { status: 400 }); + } + + // 基本参数验证 + if (!userData || typeof userData !== 'object') { + return NextResponse.json({ success: false, error: '请求参数无效' }, { status: 400 }); + } + + // 调用注册函数 + const result = await register(userData); + + // 根据结果返回响应 + if (result.success) { + // 设置CORS头 + const response = NextResponse.json({ + success: true, + message: '注册成功', + user: result.user + }, { status: 201 }); + response.headers.set('Access-Control-Allow-Origin', '*'); + response.headers.set('Access-Control-Allow-Methods', 'POST'); + response.headers.set('Access-Control-Allow-Headers', 'Content-Type'); + + return response; + } else { + // 根据不同错误类型返回不同的状态码 + let statusCode = 400; + if (result.error.includes('已被注册')) { + statusCode = 409; // 冲突状态码 + } + + return NextResponse.json({ + success: false, + error: result.error || '注册失败,请检查您的信息' + }, { status: statusCode }); + } + } catch (error) { + console.error('注册API错误:', error); + // 对于不同类型的错误返回具体错误信息 + if (error instanceof Error) { + return NextResponse.json({ + success: false, + error: error.message || '服务器内部错误' + }, { status: 500 }); + } + return NextResponse.json({ + success: false, + error: '服务器内部错误' + }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/components/auth/AuthForm.tsx b/src/components/auth/AuthForm.tsx index 81dde0c..499ac1b 100644 --- a/src/components/auth/AuthForm.tsx +++ b/src/components/auth/AuthForm.tsx @@ -6,6 +6,9 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { signIn } from 'next-auth/react'; +import { register } from '@/lib/api/actions'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Eye, EyeOff } from 'lucide-react'; interface AuthFormProps { type: 'login' | 'register'; @@ -17,6 +20,9 @@ export default function AuthForm({ type }: AuthFormProps) { const [password, setPassword] = useState(''); const [email, setEmail] = useState(''); const [minecraftUsername, setMinecraftUsername] = useState(''); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + const [showPassword, setShowPassword] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -24,39 +30,108 @@ export default function AuthForm({ type }: AuthFormProps) { try { if (type === 'login') { - // 使用next-auth的signIn功能进行登录 + // 基本验证 + if (!password) { + setError('请输入密码'); + setIsLoading(false); + return; + } + + const usernameField = username || email; + if (!usernameField) { + setError('请输入用户名或邮箱'); + setIsLoading(false); + return; + } + + // 使用next-auth的signIn功能进行登录,设置redirect为false以便处理错误 const result = await signIn('credentials', { - username: username || email, // 使用username或email作为用户名字段 + username: usernameField, // 使用username或email作为用户名字段 email: email, // 传递email字段 password, - redirect: true, // 登录成功后自动重定向 + redirect: false, // 不自动重定向,以便处理错误 callbackUrl: '/user-home' // 指定重定向目标 }); - console.log('登录结果:', result); + if (result?.error) { + // 处理登录错误 - 根据错误类型显示不同的错误信息 + console.error('登录失败:', result.error); + if (result.error.includes('CredentialsSignin')) { + setError('用户名或密码错误,请重试'); + } else if (result.error.includes('Network')) { + setError('网络连接失败,请检查您的网络设置'); + } else { + setError('登录失败,请稍后再试'); + } + } else if (result?.ok) { + // 登录成功,手动重定向 + window.location.href = '/user-home'; + } else { + setError('登录过程中发生未知错误'); + } } else { - // 注册逻辑可以在这里实现 - console.log('注册信息', { username, password, email, minecraftUsername }); - // 模拟注册请求 - await new Promise(resolve => setTimeout(resolve, 1000)); + // 调用注册API + const result = await register({ + username, + password, + email, + minecraftUsername + }); + + if (result.success) { + setSuccess('注册成功!即将跳转到登录页面...'); + // 注册成功后重定向到登录页面 + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + } else { + setError(result.error || '注册失败,请稍后再试'); + } } } catch (error) { console.error('认证失败:', error); - // 这里可以添加错误处理逻辑 + setError('网络错误,请检查您的连接'); } finally { setIsLoading(false); } }; + // 重置错误和成功消息 + const resetMessages = () => { + setError(null); + setSuccess(null); + }; + + // 监听输入变化,清除错误消息 + const handleInputChange = (setter: React.Dispatch>) => { + return (e: React.ChangeEvent) => { + resetMessages(); + setter(e.target.value); + }; + }; + return (
+ {error && ( + + 错误 + {error} + + )} + + {success && ( + + 成功 + {success} + + )}
setUsername(e.target.value)} + onChange={handleInputChange(setUsername)} placeholder={type === 'login' ? "用户名或邮箱" : "输入用户名"} />
@@ -68,20 +143,29 @@ export default function AuthForm({ type }: AuthFormProps) { id="email" type="email" value={email} - onChange={(e) => setEmail(e.target.value)} + onChange={handleInputChange(setEmail)} placeholder="或直接输入邮箱登录" /> )}
- setPassword(e.target.value)} - /> +
+ + +
{type === 'register' && ( @@ -89,22 +173,23 @@ export default function AuthForm({ type }: AuthFormProps) {
setEmail(e.target.value)} - /> + id="email" + type="email" + required + value={email} + onChange={handleInputChange(setEmail)} + />
setMinecraftUsername(e.target.value)} - /> + id="minecraftUsername" + type="text" + required + value={minecraftUsername} + onChange={handleInputChange(setMinecraftUsername)} + placeholder="您的Minecraft游戏内用户名" + />
)} diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..39eb5da --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,51 @@ +// src/components/ui/alert.tsx +import React from 'react'; + +interface AlertProps { + children: React.ReactNode; + variant?: 'default' | 'destructive'; + className?: string; +} + +interface AlertTitleProps { + children: React.ReactNode; + className?: string; +} + +interface AlertDescriptionProps { + children: React.ReactNode; + className?: string; +} + +// 主Alert组件 +export function Alert({ children, variant = 'default', className = '' }: AlertProps) { + const baseClasses = 'p-4 rounded-lg border flex items-start space-x-3'; + const variantClasses = { + default: 'bg-green-50 border-green-200 text-green-800', + destructive: 'bg-red-50 border-red-200 text-red-800' + }; + + return ( +
+ {children} +
+ ); +} + +// Alert标题组件 +export function AlertTitle({ children, className = '' }: AlertTitleProps) { + return ( +

+ {children} +

+ ); +} + +// Alert描述组件 +export function AlertDescription({ children, className = '' }: AlertDescriptionProps) { + return ( +

+ {children} +

+ ); +} \ No newline at end of file