diff --git a/package.json b/package.json index ceda37c..661f60e 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "eject": "react-scripts eject", "minify-svgs": "node scripts/minify-svgs.js", - "dev:electron": "concurrently \"react-scripts start\" \"ELECTRON_START_URL=http://192.168.68.53:3000 electron src/electron/main.js\"", + "dev:electron": "concurrently \"react-scripts start\" \"ELECTRON_START_URL=http://192.168.68.53:3000 electron public/electron.js\"", "build:electron": "npm run build && electron-builder" }, "eslintConfig": { diff --git a/src/App.css b/src/App.css index fa9a003..02a567e 100644 --- a/src/App.css +++ b/src/App.css @@ -68,6 +68,10 @@ line-height: 32.5px; } +.loading-modal .ant-modal-footer { +display: none; +} + :root { --unit-100vh: 100vh; diff --git a/src/components/Dashboard/Production/ProductionOverview.jsx b/src/components/Dashboard/Production/ProductionOverview.jsx index 807d243..e6183de 100644 --- a/src/components/Dashboard/Production/ProductionOverview.jsx +++ b/src/components/Dashboard/Production/ProductionOverview.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useCallback } from 'react' +import React, { useEffect, useState, useCallback, useContext } from 'react' import { Descriptions, Space, @@ -21,10 +21,12 @@ import ReloadIcon from '../../Icons/ReloadIcon' import useCollapseState from '../hooks/useCollapseState' import config from '../../../config' +import { AuthContext } from '../context/AuthContext' const { Title, Text } = Typography const ProductionOverview = () => { + const { token } = useContext(AuthContext) const [messageApi, contextHolder] = message.useMessage() const [error, setError] = useState(null) const [fetchPrinterStatsLoading, setFetchPrinterStatsLoading] = useState(true) @@ -68,9 +70,9 @@ const ProductionOverview = () => { setFetchPrinterStatsLoading(true) const response = await axios.get(`${config.backendUrl}/printers/stats`, { headers: { - Accept: 'application/json' - }, - withCredentials: true + Accept: 'application/json', + Authorization: `Bearer ${token}` + } }) const printStats = response.data setStats((prev) => ({ ...prev, printers: printStats })) @@ -88,9 +90,9 @@ const ProductionOverview = () => { setFetchPrinterStatsLoading(true) const response = await axios.get(`${config.backendUrl}/jobs/stats`, { headers: { - Accept: 'application/json' - }, - withCredentials: true + Accept: 'application/json', + Authorization: `Bearer ${token}` + } }) const jobstats = response.data setStats((prev) => ({ ...prev, jobs: jobstats })) @@ -107,9 +109,9 @@ const ProductionOverview = () => { try { const response = await axios.get(`${config.backendUrl}/stats/history`, { headers: { - Accept: 'application/json' - }, - withCredentials: true + Accept: 'application/json', + Authorization: `Bearer ${token}` + } }) setChartData(response.data) } catch (err) { @@ -118,8 +120,10 @@ const ProductionOverview = () => { } useEffect(() => { - fetchAllStats() - }, [fetchAllStats]) + if (token != null) { + fetchAllStats() + } + }, [fetchAllStats, token]) if (fetchPrinterStatsLoading || fetchPrinterStatsLoading) { return ( diff --git a/src/components/Dashboard/context/ApiServerContext.js b/src/components/Dashboard/context/ApiServerContext.js index 9133911..a472b09 100644 --- a/src/components/Dashboard/context/ApiServerContext.js +++ b/src/components/Dashboard/context/ApiServerContext.js @@ -402,9 +402,6 @@ const ApiServerProvider = ({ children }) => { sorter = {}, onDataChange } = params - if (token == null) { - return [] - } logger.debug('Fetching table data from:', type, { page, limit, @@ -456,9 +453,6 @@ 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, { @@ -571,9 +565,6 @@ const ApiServerProvider = ({ children }) => { // Download GCode file content const fetchObjectContent = async (id, type, fileName) => { - if (!token) { - return - } try { const response = await axios.get( `${config.backendUrl}/${type.toLowerCase()}s/${id}/content`, diff --git a/src/components/Dashboard/context/AuthContext.js b/src/components/Dashboard/context/AuthContext.js index d12fd19..e968710 100644 --- a/src/components/Dashboard/context/AuthContext.js +++ b/src/components/Dashboard/context/AuthContext.js @@ -7,7 +7,16 @@ import React, { useContext } from 'react' import axios from 'axios' -import { message, Modal, notification, Progress, Button, Space } from 'antd' +import { + message, + Modal, + notification, + Progress, + Button, + Space, + Typography +} from 'antd' +import { LoadingOutlined } from '@ant-design/icons' import PropTypes from 'prop-types' import ExclamationOctogonIcon from '../../Icons/ExclamationOctagonIcon' import InfoCircleIcon from '../../Icons/InfoCircleIcon' @@ -21,12 +30,16 @@ logger.setLevel(config.logLevel) const AuthContext = createContext() +const Title = Typography + const AuthProvider = ({ children }) => { const [messageApi, contextHolder] = message.useMessage() const [notificationApi, notificationContextHolder] = notification.useNotification() const [authenticated, setAuthenticated] = useState(false) const [initialized, setInitialized] = useState(false) + const [retreivedTokenFromSession, setRetreivedTokenFromSession] = + useState(false) const [loading, setLoading] = useState(false) const [token, setToken] = useState(null) const [expiresAt, setExpiresAt] = useState(null) @@ -46,7 +59,11 @@ const AuthProvider = ({ children }) => { setToken(storedToken) setExpiresAt(storedExpiresAt) setAuthenticated(true) + } else { + setAuthenticated(false) + setShowUnauthorizedModal(true) } + setRetreivedTokenFromSession(true) }, []) const logout = useCallback((redirectUri = '/login') => { @@ -61,18 +78,19 @@ const AuthProvider = ({ children }) => { // Login using query parameters const loginWithSSO = useCallback( - (redirectUri = window.location.pathname + window.location.search) => { + (redirectUri = location.pathname + location.search) => { messageApi.info('Logging in with tombutcher.work') const loginUrl = `${config.backendUrl}/auth/${isElectron ? 'app/' : ''}login?redirect_uri=${encodeURIComponent(redirectUri)}` if (isElectron) { console.log('Opening external url...') openExternalUrl(loginUrl) + setLoading(true) } else { console.log('Redirecting...') window.location.href = loginUrl } }, - [messageApi, openExternalUrl, isElectron] + [messageApi, openExternalUrl, isElectron, location.search] ) const getLoginToken = useCallback( @@ -94,6 +112,11 @@ const AuthProvider = ({ children }) => { setUserProfile(response.data) sessionStorage.setItem('authToken', response.data.access_token) sessionStorage.setItem('authExpiresAt', response.data.expires_at) + const searchParams = new URLSearchParams(location.search) + searchParams.delete('authCode') + const newSearch = searchParams.toString() + const newPath = location.pathname + (newSearch ? `?${newSearch}` : '') + navigate(newPath, { replace: true }) } else { setAuthenticated(false) setAuthError('Failed to authenticate user.') @@ -261,23 +284,20 @@ const AuthProvider = ({ children }) => { }, [expiresAt, authenticated, notificationApi, refreshToken]) useEffect(() => { - if (initialized == false) { - const authCode = - new URLSearchParams(location.search).get('authCode') || null - if (authCode != null) { - getLoginToken(authCode) - 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 }) - } - } else if (token == null) { - setShowUnauthorizedModal(true) - setAuthenticated(false) - } + const authCode = + new URLSearchParams(location.search).get('authCode') || null + if (authCode != null) { + getLoginToken(authCode) + } else if ( + token == null && + retreivedTokenFromSession == true && + initialized == false && + authCode == null + ) { setInitialized(true) + console.log('Showing unauth') + setShowUnauthorizedModal(true) + setAuthenticated(false) } }, [ checkAuthStatus, @@ -286,7 +306,8 @@ const AuthProvider = ({ children }) => { initialized, location.pathname, navigate, - token + token, + retreivedTokenFromSession ]) if (authError) { @@ -370,18 +391,22 @@ const AuthProvider = ({ children }) => { tombutcher.work to continue. - - Loading... - - } open={loading} - style={{ maxWidth: 200, top: '50%', transform: 'translateY(-50%)' }} + className={'loading-modal'} + title={false} + height={20} + style={{ maxWidth: 220, top: '50%', transform: 'translateY(-50%)' }} closable={false} maskClosable={false} footer={false} - /> + > + + + + Loading, please wait... + + + ) } diff --git a/src/components/PrivateRoute.jsx b/src/components/PrivateRoute.jsx index 7bd59cb..427e153 100644 --- a/src/components/PrivateRoute.jsx +++ b/src/components/PrivateRoute.jsx @@ -1,45 +1,16 @@ // PrivateRoute.js import PropTypes from 'prop-types' -import React, { useContext, useState, useEffect } from 'react' +import React, { useContext } from 'react' import { AuthContext } from './Dashboard/context/AuthContext' -import AuthLoading from './App/AppLoading' -import { useThemeContext } from './Dashboard/context/ThemeContext' const PrivateRoute = ({ component: Component }) => { - const { isDarkMode } = useThemeContext() - const { authenticated, loading, showSessionExpiredModal } = - useContext(AuthContext) - const [fadeIn, setFadeIn] = useState(false) - - useEffect(() => { - if (!loading) { - // Small delay to ensure smooth transition - const timer = setTimeout(() => setFadeIn(true), 50) - return () => clearTimeout(timer) - } - }, [loading]) - - // Show loading state while auth state is being determined - if (loading) { - return - } + const { authenticated, showSessionExpiredModal } = useContext(AuthContext) // Redirect to login if not authenticated return ( -
-
- {authenticated || showSessionExpiredModal ? ( - - ) : ( - - )} -
-
+ <> + {authenticated || showSessionExpiredModal ? : } + ) } diff --git a/src/components/PublicRoute.jsx b/src/components/PublicRoute.jsx index a710135..87198fa 100644 --- a/src/components/PublicRoute.jsx +++ b/src/components/PublicRoute.jsx @@ -3,15 +3,9 @@ import PropTypes from 'prop-types' import React, { useContext } from 'react' import { Navigate } from 'react-router-dom' import { AuthContext } from './Dashboard/context/AuthContext' -import AuthLoading from './App/AppLoading' const PublicRoute = ({ component: Component }) => { - const { authenticated, loading } = useContext(AuthContext) - - // Show loading state while auth state is being determined - if (loading) { - return - } + const { authenticated } = useContext(AuthContext) // Redirect to login if not authenticated return !authenticated ? (