Add AuthCallback component for handling authentication status and redirection
Some checks failed
CI - Build and Push / Build and Push Docker Image (push) Failing after 14s

This commit is contained in:
2025-10-08 13:03:16 +08:00
parent eba90288b7
commit 9802fb4481
4 changed files with 269 additions and 0 deletions

View File

@@ -140,5 +140,19 @@
"wantToJoin": "Want to Join Our Friend Links?", "wantToJoin": "Want to Join Our Friend Links?",
"joinDescription": "If you have a gaming-related website or community, we'd love to add you to our friend links network!", "joinDescription": "If you have a gaming-related website or community, we'd love to add you to our friend links network!",
"contactUs": "Contact Us" "contactUs": "Contact Us"
},
"auth": {
"success": "Login Successful!",
"failed": "Login Failed",
"error": "Authentication Error",
"processing": "Processing...",
"successMessage": "You have successfully logged in with your Steam account.",
"failedMessage": "Steam authentication was not successful. Please try again.",
"errorMessage": "An error occurred during authentication. Please try again later.",
"processingMessage": "Processing your authentication...",
"steamId": "Steam ID",
"redirecting": "Redirecting you to the home page in 5 seconds...",
"backToHome": "Back to Home",
"tryAgain": "Try Again"
} }
} }

View File

@@ -139,5 +139,19 @@
"wantToJoin": "想加入我们的友情链接吗?", "wantToJoin": "想加入我们的友情链接吗?",
"joinDescription": "如果您有游戏相关的网站或社区,我们很乐意将您添加到我们的友情链接网络中!", "joinDescription": "如果您有游戏相关的网站或社区,我们很乐意将您添加到我们的友情链接网络中!",
"contactUs": "联系我们" "contactUs": "联系我们"
},
"auth": {
"success": "登录成功!",
"failed": "登录失败",
"error": "认证错误",
"processing": "处理中...",
"successMessage": "您已成功使用 Steam 账户登录。",
"failedMessage": "Steam 认证未成功,请重试。",
"errorMessage": "认证过程中发生错误,请稍后重试。",
"processingMessage": "正在处理您的认证...",
"steamId": "Steam ID",
"redirecting": "5 秒后将跳转到首页...",
"backToHome": "返回首页",
"tryAgain": "重试"
} }
} }

View File

@@ -12,6 +12,7 @@ import Friends from './pages/Friends.tsx'
import Blog from './pages/Blog.tsx' import Blog from './pages/Blog.tsx'
import Servers from './pages/Servers.tsx' import Servers from './pages/Servers.tsx'
import Forum from './pages/Forum.tsx' import Forum from './pages/Forum.tsx'
import AuthCallback from './pages/AuthCallback.tsx'
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
@@ -26,6 +27,7 @@ createRoot(document.getElementById('root')!).render(
<Route path="/blog" element={<Blog />} /> <Route path="/blog" element={<Blog />} />
<Route path="/servers" element={<Servers />} /> <Route path="/servers" element={<Servers />} />
<Route path="/forum" element={<Forum />} /> <Route path="/forum" element={<Forum />} />
<Route path="/auth/callback" element={<AuthCallback />} />
</Routes> </Routes>
</Router> </Router>
</ServerProvider> </ServerProvider>

239
src/pages/AuthCallback.tsx Normal file
View File

@@ -0,0 +1,239 @@
import { useEffect, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import '../App.css'
interface AuthStatus {
status: 'success' | 'failed' | 'error' | null
steamId?: string
message?: string
}
function AuthCallback() {
const { t } = useTranslation()
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const [authStatus, setAuthStatus] = useState<AuthStatus>({ status: null })
useEffect(() => {
// Parse query parameters
const status = searchParams.get('status') as 'success' | 'failed' | 'error' | null
const steamId = searchParams.get('steamId') || undefined
const message = searchParams.get('message') || undefined
setAuthStatus({ status, steamId, message })
// Auto-redirect to home after 5 seconds on success
if (status === 'success') {
const timer = setTimeout(() => {
navigate('/')
}, 5000)
return () => clearTimeout(timer)
}
}, [searchParams, navigate])
const getStatusIcon = () => {
switch (authStatus.status) {
case 'success':
return '✅'
case 'failed':
return '❌'
case 'error':
return '⚠️'
default:
return '🔄'
}
}
const getStatusTitle = () => {
switch (authStatus.status) {
case 'success':
return t('auth.success')
case 'failed':
return t('auth.failed')
case 'error':
return t('auth.error')
default:
return t('auth.processing')
}
}
const getStatusMessage = () => {
if (authStatus.message) {
return authStatus.message
}
switch (authStatus.status) {
case 'success':
return t('auth.successMessage')
case 'failed':
return t('auth.failedMessage')
case 'error':
return t('auth.errorMessage')
default:
return t('auth.processingMessage')
}
}
return (
<div className="auth-callback-container">
<div className="auth-callback-card">
<div className="auth-icon">{getStatusIcon()}</div>
<h1 className="auth-title">{getStatusTitle()}</h1>
<p className="auth-message">{getStatusMessage()}</p>
{authStatus.steamId && (
<div className="auth-details">
<p>
<strong>{t('auth.steamId')}:</strong> {authStatus.steamId}
</p>
</div>
)}
{authStatus.status === 'success' && (
<p className="auth-redirect">{t('auth.redirecting')}</p>
)}
<div className="auth-actions">
<button
className="btn-primary"
onClick={() => navigate('/')}
>
{t('auth.backToHome')}
</button>
{authStatus.status !== 'success' && (
<button
className="btn-secondary"
onClick={() => window.location.href = '/api/authenticator/steam/login'}
>
{t('auth.tryAgain')}
</button>
)}
</div>
</div>
<style>{`
.auth-callback-container {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.auth-callback-card {
background: white;
border-radius: 16px;
padding: 3rem;
max-width: 500px;
width: 100%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
text-align: center;
}
.auth-icon {
font-size: 4rem;
margin-bottom: 1.5rem;
animation: bounce 1s ease-in-out;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.auth-title {
font-size: 2rem;
margin-bottom: 1rem;
color: #333;
}
.auth-message {
font-size: 1.1rem;
color: #666;
margin-bottom: 2rem;
line-height: 1.6;
}
.auth-details {
background: #f5f5f5;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1.5rem;
}
.auth-details p {
margin: 0.5rem 0;
color: #444;
}
.auth-redirect {
font-size: 0.9rem;
color: #999;
font-style: italic;
margin-bottom: 1.5rem;
}
.auth-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.btn-primary, .btn-secondary {
padding: 0.75rem 2rem;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 600;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #e0e0e0;
color: #333;
}
.btn-secondary:hover {
background: #d0d0d0;
transform: translateY(-2px);
}
@media (max-width: 768px) {
.auth-callback-card {
padding: 2rem;
}
.auth-title {
font-size: 1.5rem;
}
.auth-actions {
flex-direction: column;
}
.btn-primary, .btn-secondary {
width: 100%;
}
}
`}</style>
</div>
)
}
export default AuthCallback