2025-09-20 15:37:31 +08:00

227 lines
8.7 KiB
TypeScript

"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 {
// 获取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,
};
// 检查是否有错误
if (params.error) {
setStatus("error");
setMessage(params.error_description || `OAuth error: ${params.error}`);
return;
}
// 验证必需参数
if (!params.code || !params.state) {
setStatus("error");
setMessage("Missing required OAuth parameters");
return;
}
// 解析state参数获取邀请码等信息
let stateData: any = {};
try {
stateData = JSON.parse(params.state);
} catch (e) {
console.warn('无法解析state参数:', params.state);
}
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
})
});
const result = await response.json();
if (response.ok && result.success) {
console.log('Google登录成功:', result);
setStatus("success");
setMessage("Login successful! Redirecting to dashboard...");
// 保存用户信息到localStorage
if (result.data?.user) {
localStorage.setItem('user', JSON.stringify(result.data.user));
}
if (result.data?.token) {
localStorage.setItem('token', result.data.token);
}
// 2秒后跳转到主页
setTimeout(() => {
const returnUrl = stateData.origin || '/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>
);
}