Refactor navigation and authentication flow

- Move navigation setup to AppRouter
- Prevent protected route logic to handle login 401
This commit is contained in:
yangdx
2025-03-19 19:08:09 +08:00
parent 0339273fe9
commit 99814b57d9
5 changed files with 75 additions and 46 deletions

View File

@@ -5,11 +5,9 @@ import MessageAlert from '@/components/MessageAlert'
import ApiKeyAlert from '@/components/ApiKeyAlert'
import StatusIndicator from '@/components/graph/StatusIndicator'
import { healthCheckInterval } from '@/lib/constants'
import { useBackendState, useAuthStore } from '@/stores/state'
import { useBackendState } from '@/stores/state'
import { useSettingsStore } from '@/stores/settings'
import { useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { navigationService } from '@/services/navigation'
import SiteHeader from '@/features/SiteHeader'
import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
@@ -21,22 +19,13 @@ import ApiSite from '@/features/ApiSite'
import { Tabs, TabsContent } from '@/components/ui/Tabs'
function App() {
const navigate = useNavigate();
const message = useBackendState.use.message()
// Initialize navigation service
useEffect(() => {
navigationService.setNavigate(navigate);
}, [navigate]);
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
const currentTab = useSettingsStore.use.currentTab()
const [apiKeyInvalid, setApiKeyInvalid] = useState(false)
// Health check
useEffect(() => {
const { isAuthenticated } = useAuthStore.getState();
if (!enableHealthCheck || !isAuthenticated) return
// Check immediately
useBackendState.getState().check()

View File

@@ -1,4 +1,4 @@
import { HashRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import { HashRouter as Router, Routes, Route, useNavigate } from 'react-router-dom'
import { useEffect, useState } from 'react'
import { useAuthStore } from '@/stores/state'
import { navigationService } from '@/services/navigation'
@@ -16,6 +16,12 @@ interface ProtectedRouteProps {
const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
const { isAuthenticated } = useAuthStore()
const [isChecking, setIsChecking] = useState(true)
const navigate = useNavigate()
// Set navigate function for navigation service
useEffect(() => {
navigationService.setNavigate(navigate)
}, [navigate])
useEffect(() => {
let isMounted = true; // Flag to prevent state updates after unmount
@@ -71,29 +77,33 @@ const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
// Get current path and check if it's a direct access
const currentPath = window.location.hash.slice(1); // Remove the '#' from hash
const isLoginPage = currentPath === '/login';
const isDirectAccess = !document.referrer;
// Handle direct access to root path
if (isDirectAccess && currentPath === '/') {
navigationService.resetAllApplicationState();
}
// Skip redirect if already on login page
if (isLoginPage) {
return null;
}
// Use React Router's Navigate for redirection
console.log('Not authenticated, redirecting to login');
return <Navigate to="/login" replace />;
// For non-login pages, handle state reset and navigation
if (!isLoginPage) {
// Use navigation service for redirection
console.log('Not authenticated, redirecting to login');
navigationService.navigateToLogin();
return null;
}
}
return <>{children}</>
}
const AppRouter = () => {
const AppContent = () => {
const [initializing, setInitializing] = useState(true)
const { isAuthenticated } = useAuthStore()
const navigate = useNavigate()
// Set navigate function for navigation service
useEffect(() => {
navigationService.setNavigate(navigate)
}, [navigate])
// Check token validity and auth configuration on app initialization
useEffect(() => {
@@ -152,21 +162,27 @@ const AppRouter = () => {
return null
}
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/*"
element={
<ProtectedRoute>
<App />
</ProtectedRoute>
}
/>
</Routes>
)
}
const AppRouter = () => {
return (
<ThemeProvider>
<Router>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/*"
element={
<ProtectedRoute>
<App />
</ProtectedRoute>
}
/>
</Routes>
<Toaster position="top-center" />
<AppContent />
<Toaster position="bottom-center" />
</Router>
</ThemeProvider>
)

View File

@@ -173,9 +173,12 @@ axiosInstance.interceptors.response.use(
(error: AxiosError) => {
if (error.response) {
if (error.response?.status === 401) {
// Use navigation service to handle redirection
// For login API, throw error directly
if (error.config?.url?.includes('/login')) {
throw error;
}
// For other APIs, navigate to login page
navigationService.navigateToLogin();
// Return a never-resolving promise to prevent further execution
return new Promise(() => {});
}

View File

@@ -48,7 +48,7 @@ const LoginPage = () => {
toast.info(status.message)
}
navigate('/')
return; // Exit early, no need to set checkingAuth to false
return // Exit early, no need to set checkingAuth to false
}
} catch (error) {
console.error('Failed to check auth configuration:', error)
@@ -95,11 +95,17 @@ const LoginPage = () => {
} else {
toast.success(t('login.successMessage'))
}
// Navigate to home page after successful login
navigate('/')
} catch (error) {
console.error('Login failed...', error)
toast.error(t('login.errorInvalidCredentials'))
// Clear any existing auth state
useAuthStore.getState().logout()
// Clear local storage
localStorage.removeItem('LIGHTRAG-API-TOKEN')
} finally {
setLoading(false)
}

View File

@@ -61,13 +61,28 @@ class NavigationService {
* @param skipReset whether to skip state reset (used for direct access scenario where reset is already handled)
*/
navigateToLogin() {
this.resetAllApplicationState();
useAuthStore.getState().logout();
if (this.navigate) {
this.navigate('/login');
if (!this.navigate) {
console.error('Navigation function not set');
return;
}
// First navigate to login page
this.navigate('/login');
// Then reset state after navigation
setTimeout(() => {
this.resetAllApplicationState();
useAuthStore.getState().logout();
}, 0);
}
navigateToHome() {
if (!this.navigate) {
console.error('Navigation function not set');
return;
}
this.navigate('/');
}
}