108 lines
3.1 KiB
TypeScript
108 lines
3.1 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react'
|
||
import { Link, useLocation, useNavigate, useSearchParams } from 'react-router-dom'
|
||
import { Button, Result, Spin, Typography, message } from 'antd'
|
||
|
||
import { useAuth } from '@/app/providers/auth-context'
|
||
import { AuthLayout } from '@/layouts'
|
||
import { parseOAuthCallbackHash, sanitizeAuthRedirect } from '@/lib/auth/oauth'
|
||
import { getErrorMessage } from '@/lib/errors'
|
||
import { exchangeOAuthHandoff } from '@/services/auth'
|
||
|
||
const { Paragraph } = Typography
|
||
|
||
export function OAuthCallbackPage() {
|
||
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading')
|
||
const [errorMessage, setErrorMessage] = useState('')
|
||
const [searchParams] = useSearchParams()
|
||
const location = useLocation()
|
||
const navigate = useNavigate()
|
||
const { onLoginSuccess } = useAuth()
|
||
|
||
const redirect = sanitizeAuthRedirect(searchParams.get('redirect'))
|
||
const callbackPayload = useMemo(() => parseOAuthCallbackHash(location.hash), [location.hash])
|
||
|
||
useEffect(() => {
|
||
let cancelled = false
|
||
|
||
const consumeHandoff = async () => {
|
||
if (callbackPayload.status === 'error') {
|
||
if (!cancelled) {
|
||
setStatus('error')
|
||
setErrorMessage(callbackPayload.message || '第三方登录失败,请重试')
|
||
}
|
||
return
|
||
}
|
||
|
||
if (!callbackPayload.code) {
|
||
if (!cancelled) {
|
||
setStatus('error')
|
||
setErrorMessage('缺少OAuth登录交接码,请重新发起登录')
|
||
}
|
||
return
|
||
}
|
||
|
||
try {
|
||
const tokenBundle = await exchangeOAuthHandoff(callbackPayload.code)
|
||
await onLoginSuccess(tokenBundle)
|
||
|
||
if (!cancelled) {
|
||
setStatus('success')
|
||
message.success('第三方登录成功')
|
||
navigate(redirect, { replace: true })
|
||
}
|
||
} catch (error) {
|
||
if (!cancelled) {
|
||
setStatus('error')
|
||
setErrorMessage(getErrorMessage(error, '第三方登录失败,请重试'))
|
||
}
|
||
}
|
||
}
|
||
|
||
void consumeHandoff()
|
||
|
||
return () => {
|
||
cancelled = true
|
||
}
|
||
}, [callbackPayload.code, callbackPayload.message, callbackPayload.status, navigate, onLoginSuccess, redirect])
|
||
|
||
if (status === 'loading') {
|
||
return (
|
||
<AuthLayout>
|
||
<div style={{ textAlign: 'center', padding: '48px 0' }}>
|
||
<Spin size="large" />
|
||
<Paragraph type="secondary" style={{ marginTop: 16 }}>
|
||
正在完成第三方登录...
|
||
</Paragraph>
|
||
</div>
|
||
</AuthLayout>
|
||
)
|
||
}
|
||
|
||
if (status === 'success') {
|
||
return (
|
||
<AuthLayout>
|
||
<Result
|
||
status="success"
|
||
title="登录成功"
|
||
subTitle="正在跳转到目标页面..."
|
||
/>
|
||
</AuthLayout>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<AuthLayout>
|
||
<Result
|
||
status="error"
|
||
title="第三方登录失败"
|
||
subTitle={errorMessage}
|
||
extra={[
|
||
<Link key="login" to={`/login${redirect !== '/dashboard' ? `?redirect=${encodeURIComponent(redirect)}` : ''}`}>
|
||
<Button type="primary">返回登录</Button>
|
||
</Link>,
|
||
]}
|
||
/>
|
||
</AuthLayout>
|
||
)
|
||
}
|