dramaling-vocab-learning/frontend/components/shared/ProtectedRoute.tsx

76 lines
2.0 KiB
TypeScript

'use client'
import { useAuth } from '@/contexts/AuthContext'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
interface ProtectedRouteProps {
children: React.ReactNode
redirectTo?: string
fallback?: React.ReactNode
}
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
children,
redirectTo = '/login',
fallback
}) => {
const { isAuthenticated, isLoading, checkAuth } = useAuth()
const router = useRouter()
useEffect(() => {
const verifyAuth = async () => {
if (!isLoading) {
if (!isAuthenticated) {
router.push(redirectTo)
return
}
// 額外檢查 token 有效性
const isValid = await checkAuth()
if (!isValid) {
router.push(redirectTo)
}
}
}
verifyAuth()
}, [isAuthenticated, isLoading, router, redirectTo, checkAuth])
// Loading 狀態
if (isLoading) {
return (
fallback || (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
<div className="bg-white rounded-2xl shadow-xl p-8">
<div className="flex items-center space-x-4">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
<p className="text-gray-600">...</p>
</div>
</div>
</div>
)
)
}
// 未認證
if (!isAuthenticated) {
return null // 這時會觸發重定向
}
// 已認證,渲染子組件
return <>{children}</>
}
// 便利的 HOC 版本
export function withAuth<T extends object>(Component: React.ComponentType<T>, redirectTo = '/login') {
const AuthenticatedComponent = (props: T) => (
<ProtectedRoute redirectTo={redirectTo}>
<Component {...props} />
</ProtectedRoute>
)
AuthenticatedComponent.displayName = `withAuth(${Component.displayName || Component.name})`
return AuthenticatedComponent
}