Using cross-env and fixed login error handling.

This commit is contained in:
Tom Butcher 2025-08-23 22:04:39 +01:00
parent 894b6af3c8
commit 5f20f81a2c
4 changed files with 78 additions and 22 deletions

View File

@ -34,6 +34,7 @@
"antd-style": "^3.7.1", "antd-style": "^3.7.1",
"axios": "^1.11.0", "axios": "^1.11.0",
"country-list": "^2.3.0", "country-list": "^2.3.0",
"cross-env": "^10.0.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"eslint": "^9.33.0", "eslint": "^9.33.0",
@ -69,12 +70,12 @@
"main": "build/electron.js", "main": "build/electron.js",
"description": "3D Printer ERP and Control Software.", "description": "3D Printer ERP and Control Software.",
"scripts": { "scripts": {
"dev": "vite", "dev": "cross-env NODE_ENV=development vite",
"electron": "set ELECTRON_START_URL=http://0.0.0.0:3000 && NODE_ENV=development && electron .", "electron": "cross-env ELECTRON_START_URL=http://0.0.0.0:3000 && cross-env NODE_ENV=development && electron .",
"start": "serve -s build", "start": "serve -s build",
"build": "vite build", "build": "vite build",
"dev:electron": "concurrently \"vite --no-open\" \"set ELECTRON_START_URL=http://localhost:3000 && set NODE_ENV=development && electron public/electron.js\"", "dev:electron": "concurrently \"cross-env NODE_ENV=development vite --no-open\" \"cross-env ELECTRON_START_URL=http://localhost:3000 cross-env NODE_ENV=development electron public/electron.js\"",
"build:electron": "npm run build && electron-builder" "build:electron": "vite build && electron-builder"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [

View File

@ -5,12 +5,16 @@ import ExclamationOctagonIcon from '../Icons/ExclamationOctagonIcon'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import ArrowLeftIcon from '../Icons/ArrowLeftIcon' import ArrowLeftIcon from '../Icons/ArrowLeftIcon'
import ReloadIcon from '../Icons/ReloadIcon' import ReloadIcon from '../Icons/ReloadIcon'
import { useNavigate } from 'react-router-dom'
import VendorIcon from '../Icons/VendorIcon'
const AppError = ({ const AppError = ({
message = 'Error Message', message = 'Error Message',
showBack = true, showBack = true,
showRefresh = true showRefresh = true,
showHome = true
}) => { }) => {
const navigate = useNavigate()
const handleBack = () => { const handleBack = () => {
window.history.back() window.history.back()
} }
@ -19,6 +23,10 @@ const AppError = ({
window.location.reload() window.location.reload()
} }
const handleHome = () => {
navigate('/production/overview')
}
return ( return (
<> <>
<AuthParticles /> <AuthParticles />
@ -40,7 +48,7 @@ const AppError = ({
type={'error'} type={'error'}
showIcon showIcon
/> />
{(showBack || showRefresh) && ( {(showBack || showRefresh || showHome) && (
<Flex gap='middle'> <Flex gap='middle'>
{showBack && ( {showBack && (
<Button <Button
@ -56,6 +64,9 @@ const AppError = ({
size='large' size='large'
/> />
)} )}
{showHome && (
<Button icon={<VendorIcon />} onClick={handleHome} size='large' />
)}
</Flex> </Flex>
)} )}
</Flex> </Flex>
@ -66,7 +77,8 @@ const AppError = ({
AppError.propTypes = { AppError.propTypes = {
message: PropTypes.string, message: PropTypes.string,
showBack: PropTypes.bool, showBack: PropTypes.bool,
showRefresh: PropTypes.bool showRefresh: PropTypes.bool,
showHome: PropTypes.bool
} }
export default AppError export default AppError

View File

@ -21,7 +21,6 @@ import PropTypes from 'prop-types'
import ExclamationOctogonIcon from '../../Icons/ExclamationOctagonIcon' import ExclamationOctogonIcon from '../../Icons/ExclamationOctagonIcon'
import InfoCircleIcon from '../../Icons/InfoCircleIcon' import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import config from '../../../config' import config from '../../../config'
import AppError from '../../App/AppError'
import loglevel from 'loglevel' import loglevel from 'loglevel'
import { ElectronContext } from './ElectronContext' import { ElectronContext } from './ElectronContext'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
@ -30,7 +29,7 @@ logger.setLevel(config.logLevel)
const AuthContext = createContext() const AuthContext = createContext()
const Title = Typography const { Text } = Typography
const AuthProvider = ({ children }) => { const AuthProvider = ({ children }) => {
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
@ -46,6 +45,7 @@ const AuthProvider = ({ children }) => {
const [userProfile, setUserProfile] = useState(null) const [userProfile, setUserProfile] = useState(null)
const [showSessionExpiredModal, setShowSessionExpiredModal] = useState(false) const [showSessionExpiredModal, setShowSessionExpiredModal] = useState(false)
const [showUnauthorizedModal, setShowUnauthorizedModal] = useState(false) const [showUnauthorizedModal, setShowUnauthorizedModal] = useState(false)
const [showAuthErrorModal, setShowAuthErrorModal] = useState(false)
const [authError, setAuthError] = useState(null) const [authError, setAuthError] = useState(null)
const { openExternalUrl, isElectron } = useContext(ElectronContext) const { openExternalUrl, isElectron } = useContext(ElectronContext)
const location = useLocation() const location = useLocation()
@ -148,13 +148,19 @@ const AuthProvider = ({ children }) => {
} else { } else {
setAuthenticated(false) setAuthenticated(false)
setAuthError('Failed to authenticate user.') setAuthError('Failed to authenticate user.')
setShowAuthErrorModal(true)
} }
} catch (error) { } catch (error) {
logger.debug('Auth check failed', error) logger.debug('Auth check failed', error)
if (error.response?.status === 401) { if (error.response?.status === 401) {
setShowUnauthorizedModal(true) setShowUnauthorizedModal(true)
} else { } else {
setAuthError('Error connecting to authentication service.') const errorMessage =
error?.response?.data?.error ||
'Error connecting to authentication service.'
const fullStop = errorMessage.endsWith('.')
setAuthError(`${errorMessage}${!fullStop && '.'}`)
setShowAuthErrorModal(true)
} }
setAuthenticated(false) setAuthenticated(false)
} finally { } finally {
@ -186,6 +192,7 @@ const AuthProvider = ({ children }) => {
} else { } else {
setAuthenticated(false) setAuthenticated(false)
setAuthError('Failed to authenticate user.') setAuthError('Failed to authenticate user.')
setShowAuthErrorModal(true)
} }
} catch (error) { } catch (error) {
logger.debug('Auth check failed', error) logger.debug('Auth check failed', error)
@ -193,6 +200,7 @@ const AuthProvider = ({ children }) => {
setShowUnauthorizedModal(true) setShowUnauthorizedModal(true)
} else { } else {
setAuthError('Error connecting to authentication service.') setAuthError('Error connecting to authentication service.')
setShowAuthErrorModal(true)
} }
setAuthenticated(false) setAuthenticated(false)
} finally { } finally {
@ -342,10 +350,6 @@ const AuthProvider = ({ children }) => {
retreivedTokenFromSession retreivedTokenFromSession
]) ])
if (authError) {
return <AppError message={authError} showBack={false} />
}
return ( return (
<> <>
{contextHolder} {contextHolder}
@ -371,7 +375,7 @@ const AuthProvider = ({ children }) => {
Session Expired Session Expired
</Space> </Space>
} }
open={showSessionExpiredModal} open={showSessionExpiredModal && !loading && !showAuthErrorModal}
onOk={handleSessionExpiredModalOk} onOk={handleSessionExpiredModalOk}
okText='Log In' okText='Log In'
style={{ maxWidth: 430 }} style={{ maxWidth: 430 }}
@ -388,7 +392,7 @@ const AuthProvider = ({ children }) => {
</Button> </Button>
]} ]}
> >
Your session has expired. Please log in again to continue. <Text>Your session has expired. Please log in again to continue.</Text>
</Modal> </Modal>
<Modal <Modal
title={ title={
@ -397,7 +401,7 @@ const AuthProvider = ({ children }) => {
Please log in to continue Please log in to continue
</Space> </Space>
} }
open={showUnauthorizedModal} open={showUnauthorizedModal && !loading && !showAuthErrorModal}
onOk={() => { onOk={() => {
setShowUnauthorizedModal(false) setShowUnauthorizedModal(false)
loginWithSSO() loginWithSSO()
@ -419,8 +423,10 @@ const AuthProvider = ({ children }) => {
</Button> </Button>
]} ]}
> >
<Text>
You need to be logged in to access FarmControl. Please log in with You need to be logged in to access FarmControl. Please log in with
tombutcher.work to continue. tombutcher.work to continue.
</Text>
</Modal> </Modal>
<Modal <Modal
open={loading} open={loading}
@ -434,11 +440,35 @@ const AuthProvider = ({ children }) => {
> >
<Space size={'middle'}> <Space size={'middle'}>
<LoadingOutlined /> <LoadingOutlined />
<Title level={5} style={{ margin: 0 }}> <Text style={{ margin: 0 }}>Loading, please wait...</Text>
Loading, please wait...
</Title>
</Space> </Space>
</Modal> </Modal>
<Modal
title={
<Space size={'middle'}>
<ExclamationOctogonIcon />
Authentication Error
</Space>
}
open={showAuthErrorModal && !loading}
style={{ maxWidth: 430 }}
closable={false}
centered
maskClosable={false}
footer={[
<Button
key='submit'
onClick={() => {
showAuthErrorModal(false)
loginWithSSO()
}}
>
Retry Login
</Button>
]}
>
<Text>{authError}</Text>
</Modal>
</> </>
) )
} }

View File

@ -1295,6 +1295,11 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6"
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
"@epic-web/invariant@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@epic-web/invariant/-/invariant-1.0.0.tgz#1073e5dee6dd540410784990eb73e4acd25c9813"
integrity sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==
"@esbuild/aix-ppc64@0.25.9": "@esbuild/aix-ppc64@0.25.9":
version "0.25.9" version "0.25.9"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz#bef96351f16520055c947aba28802eede3c9e9a9" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz#bef96351f16520055c947aba28802eede3c9e9a9"
@ -4030,6 +4035,14 @@ crelt@^1.0.5, crelt@^1.0.6:
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
cross-env@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-10.0.0.tgz#ba25823cfa1ed6af293dcded8796fa16cd162456"
integrity sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==
dependencies:
"@epic-web/invariant" "^1.0.0"
cross-spawn "^7.0.6"
cross-spawn-windows-exe@^1.1.0, cross-spawn-windows-exe@^1.2.0: cross-spawn-windows-exe@^1.1.0, cross-spawn-windows-exe@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz#46253b0f497676e766faf4a7061004618b5ac5ec" resolved "https://registry.yarnpkg.com/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz#46253b0f497676e766faf4a7061004618b5ac5ec"