Refactor route protection by replacing ProtectedRoute with ClientRouteGuard in analytics, create-shorturl, and links pages to standardize authentication handling across the application.

This commit is contained in:
2025-04-24 01:41:44 +08:00
parent d80d5e976b
commit 3162836e91
4 changed files with 67 additions and 10 deletions

View File

@@ -14,6 +14,7 @@ import { ProjectSelector } from '@/app/components/ui/ProjectSelector';
import { TagSelector } from '@/app/components/ui/TagSelector'; import { TagSelector } from '@/app/components/ui/TagSelector';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { useShortUrlStore } from '@/app/utils/store'; import { useShortUrlStore } from '@/app/utils/store';
import ClientRouteGuard from '@/app/components/ClientRouteGuard';
// 事件类型定义 // 事件类型定义
interface Event { interface Event {
@@ -1109,6 +1110,7 @@ function AnalyticsContent() {
// Main page component with Suspense // Main page component with Suspense
export default function AnalyticsPage() { export default function AnalyticsPage() {
return ( return (
<ClientRouteGuard>
<Suspense fallback={ <Suspense fallback={
<div className="flex items-center justify-center min-h-screen"> <div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500" /> <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500" />
@@ -1116,5 +1118,6 @@ export default function AnalyticsPage() {
}> }>
<AnalyticsContent /> <AnalyticsContent />
</Suspense> </Suspense>
</ClientRouteGuard>
); );
} }

View File

@@ -0,0 +1,45 @@
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
// 这个组件会检查 localStorage 中是否有认证令牌,如果没有则重定向到登录页面
export default function ClientRouteGuard({ children }: { children: React.ReactNode }) {
const router = useRouter();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// 检查 localStorage 中是否有认证令牌
const checkAuth = () => {
// 查找 Supabase 认证令牌
const hasAuthToken = !!localStorage.getItem('sb-mwwvqwevplndzvmqmrxa-auth-token') ||
!!localStorage.getItem('sb-auth-token');
if (!hasAuthToken) {
// 如果没有令牌,重定向到登录页面
router.push('/login');
} else {
setIsAuthenticated(true);
}
setIsLoading(false);
};
checkAuth();
}, [router]);
// 显示加载状态
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500 mx-auto"></div>
<p className="mt-4 text-lg text-gray-700 dark:text-gray-300">...</p>
</div>
</div>
);
}
// 只有当用户已认证时才显示子组件
return isAuthenticated ? <>{children}</> : null;
}

View File

@@ -3,10 +3,10 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useAuth } from '@/lib/auth'; import { useAuth } from '@/lib/auth';
import { ProtectedRoute } from '@/lib/auth';
import { limqRequest } from '@/lib/api'; import { limqRequest } from '@/lib/api';
import { TeamSelector } from '@/app/components/ui/TeamSelector'; import { TeamSelector } from '@/app/components/ui/TeamSelector';
import { ProjectSelector } from '@/app/components/ui/ProjectSelector'; import { ProjectSelector } from '@/app/components/ui/ProjectSelector';
import ClientRouteGuard from '@/app/components/ClientRouteGuard';
interface ShortUrlData { interface ShortUrlData {
originalUrl: string; originalUrl: string;
@@ -21,9 +21,9 @@ interface ShortUrlData {
export default function CreateShortUrlPage() { export default function CreateShortUrlPage() {
return ( return (
<ProtectedRoute> <ClientRouteGuard>
<CreateShortUrlForm /> <CreateShortUrlForm />
</ProtectedRoute> </ClientRouteGuard>
); );
} }

View File

@@ -7,6 +7,7 @@ import { Loader2, ExternalLink, Search } from 'lucide-react';
import { TeamSelector } from '@/app/components/ui/TeamSelector'; import { TeamSelector } from '@/app/components/ui/TeamSelector';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useShortUrlStore, ShortUrlData } from '@/app/utils/store'; import { useShortUrlStore, ShortUrlData } from '@/app/utils/store';
import ClientRouteGuard from '@/app/components/ClientRouteGuard';
// Define attribute type to avoid using 'any' // Define attribute type to avoid using 'any'
interface LinkAttributes { interface LinkAttributes {
@@ -102,6 +103,14 @@ const convertClickHouseToShortLink = (data: Record<string, unknown>): ShortLink
}; };
export default function LinksPage() { export default function LinksPage() {
return (
<ClientRouteGuard>
<LinksPageContent />
</ClientRouteGuard>
);
}
function LinksPageContent() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [links, setLinks] = useState<ShortLink[]>([]); const [links, setLinks] = useState<ShortLink[]>([]);