From a20235a9537a62450642e0c7d7fbe72cc1c3b527 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 20 Jul 2025 20:20:30 +0100 Subject: [PATCH] Refactor routing logic in App component to conditionally use HashRouter or BrowserRouter based on the environment. Update AuthContext and related components to replace 'authenticated' with 'token' for improved authentication handling. Enhance NotesPanel and ObjectTable components to manage loading states and data fetching more effectively, ensuring better user experience and error handling. --- src/App.jsx | 14 +++++- .../Dashboard/common/EditObjectForm.jsx | 6 +-- .../Dashboard/common/NotesPanel.jsx | 45 ++++++++++--------- .../Dashboard/common/ObjectTable.jsx | 34 +++++++------- .../Dashboard/context/ApiServerContext.js | 23 +++++++--- .../Dashboard/context/AuthContext.js | 41 +++++++++++------ 6 files changed, 102 insertions(+), 61 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 591ff2f..2a84457 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,7 @@ import React from 'react' import { - BrowserRouter as Router, + HashRouter, + BrowserRouter, Routes, Route, Navigate @@ -70,8 +71,19 @@ import Hosts from './components/Dashboard/Management/Hosts.jsx' import { ElectronProvider } from './components/Dashboard/context/ElectronContext.js' import AuthCallback from './components/App/AuthCallback.jsx' +const getRouter = () => { + if ( + typeof window !== 'undefined' && + window.location.href.includes('index.html') + ) { + return HashRouter + } + return BrowserRouter +} + const AppContent = () => { const { themeConfig } = useThemeContext() + const Router = getRouter() return ( diff --git a/src/components/Dashboard/common/EditObjectForm.jsx b/src/components/Dashboard/common/EditObjectForm.jsx index cbebdbb..1d162f1 100644 --- a/src/components/Dashboard/common/EditObjectForm.jsx +++ b/src/components/Dashboard/common/EditObjectForm.jsx @@ -44,7 +44,7 @@ const EditObjectForm = ({ id, type, style, children }) => { subscribeToObject, subscribeToLock } = useContext(ApiServerContext) - const { authenticated } = useContext(AuthContext) + const { token } = useContext(AuthContext) // Validate form on change useEffect(() => { form @@ -92,11 +92,11 @@ const EditObjectForm = ({ id, type, style, children }) => { }, []) useEffect(() => { - if (!initialized && id && authenticated == true) { + if (!initialized && id && token != null) { setInitialized(true) handleFetchObject() } - }, [id, initialized, handleFetchObject, authenticated]) + }, [id, initialized, handleFetchObject, token]) useEffect(() => { if (id && connected) { diff --git a/src/components/Dashboard/common/NotesPanel.jsx b/src/components/Dashboard/common/NotesPanel.jsx index 0542123..53ebfd4 100644 --- a/src/components/Dashboard/common/NotesPanel.jsx +++ b/src/components/Dashboard/common/NotesPanel.jsx @@ -264,6 +264,7 @@ const NotesPanel = ({ _id, onNewNote, type }) => { const [newNoteOpen, setNewNoteOpen] = useState(false) const [showMarkdown, setShowMarkdown] = useState(false) const [loading, setLoading] = useState(true) + const [initialized, setInitialized] = useState(false) const [messageApi, contextHolder] = message.useMessage() const [newNoteFormLoading, setNewNoteFormLoading] = useState(false) const [newNoteFormValues, setNewNoteFormValues] = useState({}) @@ -290,19 +291,17 @@ const NotesPanel = ({ _id, onNewNote, type }) => { .catch(() => setDoneEnabled(false)) }, [newNoteForm, newNoteFormUpdateValues]) - const { authenticated, userProfile } = useContext(AuthContext) + const { token, userProfile } = useContext(AuthContext) const { fetchNotes } = useContext(ApiServerContext) const fetchData = useCallback( async (id) => { try { const newData = await fetchNotes(id) - setLoading(false) return newData } catch (error) { - setNotes([]) setError(error) - setLoading(false) + return null } }, [fetchNotes] @@ -322,24 +321,27 @@ const NotesPanel = ({ _id, onNewNote, type }) => { const generateNotes = useCallback( async (id) => { const notesData = await fetchData(id) + setLoading(false) + + if (notesData == null) { + return null + } if (notesData.length <= 0) { return ( - } spinning={loading}> - - - - - - No notes added. - - - + + + + + + No notes added. + + ) } @@ -458,10 +460,11 @@ const NotesPanel = ({ _id, onNewNote, type }) => { }, [_id, generateNotes]) useEffect(() => { - if (authenticated) { + if (token != null && !initialized) { handleReloadData() + setInitialized(true) } - }, [authenticated, handleReloadData]) + }, [token, handleReloadData, initialized]) const handleModalOk = async () => { try { diff --git a/src/components/Dashboard/common/ObjectTable.jsx b/src/components/Dashboard/common/ObjectTable.jsx index ba566d8..0ef33a9 100644 --- a/src/components/Dashboard/common/ObjectTable.jsx +++ b/src/components/Dashboard/common/ObjectTable.jsx @@ -56,7 +56,7 @@ const ObjectTable = forwardRef( }, ref ) => { - const { authenticated } = useContext(AuthContext) + const { token } = useContext(AuthContext) const { fetchObjects, connected, subscribeToObject, subscribeToType } = useContext(ApiServerContext) const isMobile = useMediaQuery({ maxWidth: 768 }) @@ -163,7 +163,7 @@ const ObjectTable = forwardRef( setLoading(false) setLazyLoading(false) - return result.data + return result.data || [] } catch (error) { setPages((prev) => prev.map((page) => ({ @@ -302,18 +302,20 @@ const ObjectTable = forwardRef( // Subscribe to each item in all pages pages.forEach((page) => { - page.items.forEach((item) => { - if (!item.isSkeleton) { - const unsubscribe = subscribeToObject( - item._id, - type, - updateEventHandler - ) - if (unsubscribe) { - unsubscribes.push(unsubscribe) + if (page?.items && page?.items?.length > 0) { + page.items.forEach((item) => { + if (!item.isSkeleton) { + const unsubscribe = subscribeToObject( + item._id, + type, + updateEventHandler + ) + if (unsubscribe) { + unsubscribes.push(unsubscribe) + } } - } - }) + }) + } }) return () => { @@ -378,11 +380,11 @@ const ObjectTable = forwardRef( })) useEffect(() => { - if (authenticated && !pages.includes(initialPage) && !initialized) { + if (token != null && !pages.includes(initialPage) && !initialized) { loadInitialPage() setInitialized(true) } - }, [authenticated, loadInitialPage, initialPage, pages, initialized]) + }, [token, loadInitialPage, initialPage, pages, initialized]) const getFilterDropdown = ({ setSelectedKeys, @@ -497,7 +499,7 @@ const ObjectTable = forwardRef( fixed: fixed, key: prop.name, render: (text, record) => { - if (record.isSkeleton) { + if (record?.isSkeleton) { return ( ) diff --git a/src/components/Dashboard/context/ApiServerContext.js b/src/components/Dashboard/context/ApiServerContext.js index 240d074..9133911 100644 --- a/src/components/Dashboard/context/ApiServerContext.js +++ b/src/components/Dashboard/context/ApiServerContext.js @@ -23,7 +23,8 @@ logger.setLevel(config.logLevel) const ApiServerContext = createContext() const ApiServerProvider = ({ children }) => { - const { token, userProfile, authenticated } = useContext(AuthContext) + const { token, userProfile, authenticated, setUnauthenticated } = + useContext(AuthContext) const socketRef = useRef(null) const [connected, setConnected] = useState(false) const [connecting, setConnecting] = useState(false) @@ -347,6 +348,11 @@ const ApiServerProvider = ({ children }) => { ) const showError = (error, callback = null) => { + const code = error.response.data.code || 'UNKNOWN' + if (code == 'UNAUTHORIZED') { + setUnauthenticated() + return + } var content = `Error ${error.code} (${error.status}): ${error.message}` if (error.response?.data?.error) { content = `${error.response?.data?.error} (${error.status})` @@ -377,14 +383,13 @@ const ApiServerProvider = ({ children }) => { Authorization: `Bearer ${token}` } }) + setFetchLoading(false) return response.data } catch (err) { showError(err, () => { fetchObject(id, type) }) return {} - } finally { - setFetchLoading(false) } } @@ -397,7 +402,9 @@ const ApiServerProvider = ({ children }) => { sorter = {}, onDataChange } = params - + if (token == null) { + return [] + } logger.debug('Fetching table data from:', type, { page, limit, @@ -449,6 +456,9 @@ const ApiServerProvider = ({ children }) => { // Fetch table data with pagination, filtering, and sorting const fetchObjectsByProperty = async (type, params = {}) => { + if (token == null) { + return [] + } const { filter = {}, properties = [] } = params logger.debug('Fetching property object data from:', type, { @@ -476,8 +486,9 @@ const ApiServerProvider = ({ children }) => { return newData } catch (err) { showError(err, () => { - fetchObjectsByProperty(type, params) + fetchObjects(type, params) }) + return [] } } @@ -604,7 +615,7 @@ const ApiServerProvider = ({ children }) => { } }) - const notesData = response.data + const notesData = response.data || [] logger.debug('Fetched notes:', notesData.length) return notesData } catch (error) { diff --git a/src/components/Dashboard/context/AuthContext.js b/src/components/Dashboard/context/AuthContext.js index a52523a..d12fd19 100644 --- a/src/components/Dashboard/context/AuthContext.js +++ b/src/components/Dashboard/context/AuthContext.js @@ -15,7 +15,7 @@ import config from '../../../config' import AppError from '../../App/AppError' import loglevel from 'loglevel' import { ElectronContext } from './ElectronContext' -import { useLocation } from 'react-router-dom' +import { useLocation, useNavigate } from 'react-router-dom' const logger = loglevel.getLogger('ApiServerContext') logger.setLevel(config.logLevel) @@ -36,6 +36,7 @@ const AuthProvider = ({ children }) => { const [authError, setAuthError] = useState(null) const { openExternalUrl, isElectron } = useContext(ElectronContext) const location = useLocation() + const navigate = useNavigate() // Read token from session storage if present useEffect(() => { @@ -147,6 +148,11 @@ const AuthProvider = ({ children }) => { } }, [token]) + const setUnauthenticated = () => { + setAuthenticated(false) + setShowUnauthorizedModal(true) + } + const refreshToken = useCallback(async () => { try { const response = await axios.get(`${config.backendUrl}/auth/refresh`, { @@ -260,22 +266,28 @@ const AuthProvider = ({ children }) => { new URLSearchParams(location.search).get('authCode') || null if (authCode != null) { getLoginToken(authCode) - if (window && window.history && window.location) { - const url = new URL(window.location.href) - if (url.searchParams.has('authCode')) { - url.searchParams.delete('authCode') - window.history.replaceState( - {}, - document.title, - url.pathname + url.search - ) - } + const searchParams = new URLSearchParams(location.search) + if (searchParams.has('authCode')) { + searchParams.delete('authCode') + const newSearch = searchParams.toString() + const newPath = location.pathname + (newSearch ? `?${newSearch}` : '') + navigate(newPath, { replace: true }) } - setInitialized(true) - return + } else if (token == null) { + setShowUnauthorizedModal(true) + setAuthenticated(false) } + setInitialized(true) } - }, [checkAuthStatus, location.search, getLoginToken, initialized]) + }, [ + checkAuthStatus, + location.search, + getLoginToken, + initialized, + location.pathname, + navigate, + token + ]) if (authError) { return @@ -288,6 +300,7 @@ const AuthProvider = ({ children }) => {