casualroom/apps/oa/web/app/[locale]/auth/callback/page.tsx

172 lines
7.4 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 { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { toast } from "@nice/ui/components/sonner";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@nice/ui/components/card";
import { Button } from "@nice/ui/components/button";
import { authClient } from "@/lib/auth-client";
export default function AuthCallbackPage() {
const router = useRouter();
const searchParams = useSearchParams();
const [status, setStatus] = useState<'processing' | 'success' | 'error'>('processing');
const [message, setMessage] = useState('正在处理OAuth认证...');
const [userSession, setUserSession] = useState<any>(null);
useEffect(() => {
const handleOAuthCallback = async () => {
try {
// 获取URL参数检查是否有错误
const error = searchParams.get('error');
const errorDescription = searchParams.get('error_description');
const code = searchParams.get('code');
console.log('OAuth回调参数:', {
error,
errorDescription,
code,
url: window.location.href
});
if (error) {
throw new Error(errorDescription || `OAuth错误: ${error}`);
}
if (!code) {
throw new Error('未收到授权码请重新开始OAuth流程');
}
setMessage('收到授权码,正在验证会话...');
// Better Auth会自动处理OAuth回调我们立即开始检查会话
// 检查会话状态
let attempts = 0;
const maxAttempts = 10;
while (attempts < maxAttempts) {
attempts++;
try {
const session = await authClient.getSession();
if (session.data?.user) {
setStatus('success');
setMessage('OAuth认证成功');
setUserSession(session.data);
toast.success('登录成功!');
// 智能重定向逻辑
let redirectTo = '/';
// 延迟跳转
setTimeout(() => {
router.push(redirectTo);
}, 1500);
return;
}
} catch (err) {
console.log(`会话检查失败 (尝试 ${attempts}):`, err);
}
// 如果还没有会话,等待一段时间再重试
if (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
throw new Error('OAuth认证超时未能建立用户会话');
} catch (error) {
console.error('OAuth回调处理失败:', error);
setStatus('error');
setMessage(error instanceof Error ? error.message : '认证失败');
toast.error(`认证失败: ${error instanceof Error ? error.message : '未知错误'}`);
}
};
// 延迟执行回调处理,确保页面已加载
const timer = setTimeout(handleOAuthCallback, 100);
return () => clearTimeout(timer);
}, [searchParams, router]);
const handleRetry = () => {
// 重新尝试时也使用相同的重定向逻辑
const redirectTo = searchParams.get('state') || '/';
router.push(redirectTo);
};
return (
<div className="container mx-auto p-6 max-w-lg">
<Card>
<CardHeader className="text-center">
<div className="mx-auto mb-4">
{status === 'processing' && (
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto"></div>
)}
{status === 'success' && (
<div className="rounded-full h-12 w-12 bg-green-100 dark:bg-green-900 mx-auto flex items-center justify-center">
<svg className="h-6 w-6 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
)}
{status === 'error' && (
<div className="rounded-full h-12 w-12 bg-red-100 dark:bg-red-900 mx-auto flex items-center justify-center">
<svg className="h-6 w-6 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
)}
</div>
<CardTitle>
{status === 'processing' && 'OAuth认证中'}
{status === 'success' && '认证成功'}
{status === 'error' && '认证失败'}
</CardTitle>
<CardDescription>
{message}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{userSession && (
<div className="bg-green-50 dark:bg-green-900/20 p-4 rounded-md">
<h3 className="font-semibold mb-2">:</h3>
<div className="text-sm space-y-1">
<div><strong>:</strong> {userSession.user.email}</div>
<div><strong>:</strong> {userSession.user.name}</div>
<div><strong>ID:</strong> {userSession.user.id}</div>
</div>
</div>
)}
{status === 'success' && (
<div className="text-center text-sm text-muted-foreground">
...
</div>
)}
{status === 'error' && (
<Button onClick={handleRetry} className="w-full">
</Button>
)}
<div className="text-xs text-muted-foreground">
<p className="mb-2"><strong>:</strong></p>
<ul className="list-disc list-inside space-y-1">
<li>OAuth2/OIDC认证回调</li>
<li>Better Auth自动处理令牌交换</li>
<li></li>
</ul>
</div>
</CardContent>
</Card>
</div>
);
}