2025-09-21 01:12:25 +08:00

243 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { CheckCircle, XCircle, Loader2, AlertTriangle } from "lucide-react";
import { loginWithGoogleToken } from "@/lib/auth";
import type { OAuthCallbackParams, OAuthState } from "@/app/types/google-oauth";
export default function OAuthCallback() {
const router = useRouter();
const searchParams = useSearchParams();
const [status, setStatus] = useState<"loading" | "success" | "error" | "conflict">("loading");
const [message, setMessage] = useState("");
const [conflictData, setConflictData] = useState<any>(null);
useEffect(() => {
const handleOAuthCallback = async () => {
try {
console.log('OAuth回调页面开始处理...');
// 获取URL参数
const params: OAuthCallbackParams = {
code: searchParams.get("code") || undefined,
state: searchParams.get("state") || undefined,
error: searchParams.get("error") || undefined,
error_description: searchParams.get("error_description") || undefined,
};
console.log('获取到的URL参数:', params);
// 检查是否有错误
if (params.error) {
console.error('OAuth错误:', params.error, params.error_description);
setStatus("error");
setMessage(params.error_description || `OAuth error: ${params.error}`);
return;
}
// 验证必需参数
if (!params.code || !params.state) {
console.error('缺少必需的OAuth参数:', { code: !!params.code, state: !!params.state });
setStatus("error");
setMessage("Missing required OAuth parameters");
return;
}
// 解析state参数获取邀请码等信息
let stateData: any = {};
try {
stateData = JSON.parse(params.state);
console.log('解析后的State数据:', stateData);
} catch (e) {
console.warn('无法解析state参数:', params.state, e);
}
console.log('开始处理Google OAuth回调, code:', params.code?.substring(0, 20) + '...');
console.log('State数据:', stateData);
// 调用后端处理授权码
const response = await fetch('/api/auth/google/callback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
code: params.code,
state: params.state,
inviteCode: stateData.inviteCode || undefined
})
});
if (!response.ok) {
// 处理HTTP错误状态
const errorText = await response.text();
console.error('OAuth API调用失败:', response.status, errorText);
throw new Error(`OAuth API调用失败 (${response.status}): ${errorText}`);
}
const result = await response.json();
console.log('OAuth API响应:', result);
if (result.success) {
console.log('Google登录成功:', result);
setStatus("success");
setMessage("Login successful! Redirecting to dashboard...");
// 保存用户信息到localStorage (使用认证库的统一键名)
if (result.data?.user) {
localStorage.setItem('currentUser', JSON.stringify(result.data.user));
}
if (result.data?.token) {
localStorage.setItem('token', result.data.token);
}
// 2秒后跳转到主页
setTimeout(() => {
// 修复: Google登录成功后应该跳转到主页面而不是来源页面
const returnUrl = '/movies';
window.location.href = returnUrl;
}, 2000);
} else {
throw new Error(result.message || 'Google登录失败');
}
} catch (error: any) {
console.error("OAuth callback error:", error);
if (error.type === 'EMAIL_CONFLICT') {
setStatus("conflict");
setMessage(error.message);
setConflictData(error.data);
} else {
setStatus("error");
setMessage(error.message || "OAuth callback processing failed");
}
}
};
handleOAuthCallback();
}, [searchParams, router]);
const handleBindAccount = async () => {
try {
// 这里应该实现账户绑定逻辑
// 需要调用 /api/auth/google/bind 接口
console.log("Account binding not yet implemented");
setMessage("Account binding feature is not yet implemented");
} catch (error: any) {
console.error("Account binding failed:", error);
setMessage(error.message || "Account binding failed");
}
};
const handleReturnToLogin = () => {
router.push("/login");
};
const renderContent = () => {
switch (status) {
case "loading":
return (
<div className="flex flex-col items-center justify-center space-y-4">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-cyan-400 to-purple-600 rounded-full blur-lg opacity-30 animate-pulse"></div>
<Loader2 className="h-16 w-16 animate-spin text-cyan-400 relative z-10" />
</div>
<p className="text-lg text-gray-300">Processing OAuth callback...</p>
</div>
);
case "success":
return (
<div className="flex flex-col items-center justify-center space-y-4">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-cyan-400 to-purple-600 rounded-full blur-lg opacity-30"></div>
<CheckCircle className="h-16 w-16 text-cyan-400 relative z-10" />
</div>
<h2 className="text-2xl font-semibold bg-gradient-to-r from-cyan-400 to-purple-600 bg-clip-text text-transparent">
Login Successful
</h2>
<p className="text-gray-300 text-center max-w-md">{message}</p>
</div>
);
case "conflict":
return (
<div className="flex flex-col items-center justify-center space-y-4">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-yellow-400 to-orange-500 rounded-full blur-lg opacity-30"></div>
<AlertTriangle className="h-16 w-16 text-yellow-400 relative z-10" />
</div>
<h2 className="text-2xl font-semibold bg-gradient-to-r from-yellow-400 to-orange-500 bg-clip-text text-transparent">
Account Conflict
</h2>
<p className="text-gray-300 text-center max-w-md">{message}</p>
{conflictData && (
<div className="text-center space-y-4">
<p className="text-sm text-gray-400">
Email: {conflictData.existingUser?.email}
</p>
<div className="flex gap-3">
<button
onClick={handleBindAccount}
className="px-4 py-2 bg-gradient-to-r from-cyan-400 to-purple-600 text-white rounded-lg hover:from-cyan-500 hover:to-purple-700 transition-all duration-300"
>
Bind Account
</button>
<button
onClick={handleReturnToLogin}
className="px-4 py-2 border border-white/20 text-white rounded-lg hover:bg-white/10 transition-all duration-300"
>
Return to Login
</button>
</div>
</div>
)}
</div>
);
case "error":
return (
<div className="flex flex-col items-center justify-center space-y-4">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-red-500 to-orange-500 rounded-full blur-lg opacity-30"></div>
<XCircle className="h-16 w-16 text-red-400 relative z-10" />
</div>
<h2 className="text-2xl font-semibold bg-gradient-to-r from-red-500 to-orange-500 bg-clip-text text-transparent">
OAuth Failed
</h2>
<p className="text-gray-300 text-center max-w-md">{message}</p>
<button
onClick={handleReturnToLogin}
className="mt-4 px-6 py-2 bg-gradient-to-r from-cyan-400 to-purple-600 text-white rounded-lg hover:from-cyan-500 hover:to-purple-700 transition-all duration-300 shadow-lg hover:shadow-xl transform hover:scale-105"
>
Return to Login
</button>
</div>
);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-black via-gray-900 to-black px-4">
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-sm rounded-2xl shadow-2xl p-8 max-w-md w-full border border-gray-700/50 relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-cyan-400/10 to-purple-600/10 pointer-events-none"></div>
<div className="relative z-10">
{status === "loading" && (
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-white mb-2 bg-gradient-to-r from-cyan-400 to-purple-600 bg-clip-text text-transparent">
OAuth Callback
</h1>
<p className="text-gray-300">
Please wait while we process your authentication
</p>
</div>
)}
{renderContent()}
</div>
</div>
</div>
);
}