Merge pull request #1849 from RafaelTaranto/chore/migrete-routing

LAM-1422 chore: update react and routing
This commit is contained in:
Rafael Taranto 2025-05-16 07:08:51 +01:00 committed by GitHub
commit 960a12071c
30 changed files with 4158 additions and 2851 deletions

6580
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,7 @@
"d3": "^6.2.0", "d3": "^6.2.0",
"date-fns": "^2.26.0", "date-fns": "^2.26.0",
"date-fns-tz": "^1.1.6", "date-fns-tz": "^1.1.6",
"downshift": "3.3.4", "downshift": "9.0.9",
"file-saver": "2.0.2", "file-saver": "2.0.2",
"formik": "2.2.0", "formik": "2.2.0",
"jss-plugin-extend": "^10.0.0", "jss-plugin-extend": "^10.0.0",
@ -28,17 +28,18 @@
"pretty-ms": "^2.1.0", "pretty-ms": "^2.1.0",
"qrcode.react": "4.2.0", "qrcode.react": "4.2.0",
"ramda": "^0.26.1", "ramda": "^0.26.1",
"react": "17.0.2", "react": "18.3.1",
"react-copy-to-clipboard": "^5.0.2", "react-copy-to-clipboard": "^5.0.2",
"react-dom": "17.0.2", "react-dom": "18.3.1",
"react-dropzone": "^11.4.2", "react-dropzone": "^11.4.2",
"react-number-format": "^4.4.1", "react-number-format": "^4.4.1",
"react-otp-input": "3.1.1", "react-otp-input": "3.1.1",
"react-router-dom": "5.1.2",
"react-virtualized": "^9.21.2", "react-virtualized": "^9.21.2",
"ua-parser-js": "1.0.40", "ua-parser-js": "1.0.40",
"uuid": "11.1.0", "uuid": "11.1.0",
"yup": "1.6.1" "wouter": "^3.7.0",
"yup": "1.6.1",
"zustand": "^4.5.7"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/vite": "^4.1.4", "@tailwindcss/vite": "^4.1.4",

View file

@ -1,7 +1,7 @@
import CssBaseline from '@mui/material/CssBaseline' import CssBaseline from '@mui/material/CssBaseline'
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles' import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles'
import React, { useState } from 'react' import React, { useState } from 'react'
import { BrowserRouter as Router } from 'react-router-dom' import { Router } from 'wouter'
import ApolloProvider from './utils/apollo' import ApolloProvider from './utils/apollo'
import AppContext from './AppContext' import AppContext from './AppContext'
@ -9,10 +9,12 @@ import theme from './styling/theme'
import Main from './Main' import Main from './Main'
import './styling/global/global.css' import './styling/global/global.css'
import useLocationWithConfirmation from './routing/useLocationWithConfirmation.js'
const App = () => { const App = () => {
const [wizardTested, setWizardTested] = useState(false) const [wizardTested, setWizardTested] = useState(false)
const [userData, setUserData] = useState(null) const [userData, setUserData] = useState(null)
const [isDirtyForm, setDirtyForm] = useState(false)
const setRole = role => { const setRole = role => {
if (userData && role && userData.role !== role) { if (userData && role && userData.role !== role) {
@ -22,8 +24,16 @@ const App = () => {
return ( return (
<AppContext.Provider <AppContext.Provider
value={{ wizardTested, setWizardTested, userData, setUserData, setRole }}> value={{
<Router> wizardTested,
setWizardTested,
userData,
setUserData,
setRole,
isDirtyForm,
setDirtyForm,
}}>
<Router hook={useLocationWithConfirmation}>
<ApolloProvider> <ApolloProvider>
<StyledEngineProvider enableCssLayer> <StyledEngineProvider enableCssLayer>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>

View file

@ -1,5 +1,5 @@
import { useHistory, useLocation } from 'react-router-dom' import { useLocation } from 'wouter'
import React, { useContext } from 'react' import React, { useContext, useState } from 'react'
import { gql, useQuery } from '@apollo/client' import { gql, useQuery } from '@apollo/client'
import Slide from '@mui/material/Slide' import Slide from '@mui/material/Slide'
import Grid from '@mui/material/Grid' import Grid from '@mui/material/Grid'
@ -8,6 +8,7 @@ import Header from './components/layout/Header.jsx'
import Sidebar from './components/layout/Sidebar.jsx' import Sidebar from './components/layout/Sidebar.jsx'
import TitleSection from './components/layout/TitleSection.jsx' import TitleSection from './components/layout/TitleSection.jsx'
import { getParent, hasSidebar, Routes, tree } from './routing/routes.jsx' import { getParent, hasSidebar, Routes, tree } from './routing/routes.jsx'
import Wizard from './pages/Wizard/Wizard.jsx'
import AppContext from './AppContext.js' import AppContext from './AppContext.js'
@ -26,35 +27,42 @@ const GET_USER_DATA = gql`
` `
const Main = () => { const Main = () => {
const location = useLocation() const [location, navigate] = useLocation()
const history = useHistory()
const { wizardTested, userData, setUserData } = useContext(AppContext) const { wizardTested, userData, setUserData } = useContext(AppContext)
const [loading, setLoading] = useState(true)
const { loading } = useQuery(GET_USER_DATA, { useQuery(GET_USER_DATA, {
onCompleted: userResponse => { onCompleted: userResponse => {
if (!userData && userResponse?.userData) if (!userData && userResponse?.userData) {
setUserData(userResponse.userData) setUserData(userResponse.userData)
}
setLoading(false)
}, },
}) })
const route = location.pathname const sidebar = hasSidebar(location)
const parent = sidebar ? getParent(location) : {}
const sidebar = hasSidebar(route) const is404 = location === '/404'
const parent = sidebar ? getParent(route) : {}
const is404 = location.pathname === '/404' const isSelected = it => location === it.route
const isSelected = it => location.pathname === it.route const onClick = it => navigate(it.route)
const onClick = it => history.push(it.route)
const contentClassName = sidebar ? 'flex-1 ml-12 pt-4' : 'w-[1200px]' const contentClassName = sidebar ? 'flex-1 ml-12 pt-4' : 'w-[1200px]'
// Show loading state until userData is fetched
if (loading) {
return <></>
}
if (!wizardTested && !is404 && userData) {
return <Wizard />
}
return ( return (
<div className="flex flex-col w-full min-h-full"> <div className="flex flex-col w-full min-h-full">
{!is404 && wizardTested && userData && ( {!is404 && wizardTested && <Header tree={tree} user={userData} />}
<Header tree={tree} user={userData} />
)}
<main className="flex flex-1 flex-col my-0 mx-auto h-full w-[1200px]"> <main className="flex flex-1 flex-col my-0 mx-auto h-full w-[1200px]">
{sidebar && !is404 && wizardTested && ( {sidebar && !is404 && wizardTested && (
<Slide direction="left" in={true} mountOnEnter unmountOnExit> <Slide direction="left" in={true} mountOnEnter unmountOnExit>
@ -73,7 +81,9 @@ const Main = () => {
onClick={onClick} onClick={onClick}
/> />
)} )}
<div className={contentClassName}>{!loading && <Routes />}</div> <div className={contentClassName}>
<Routes />
</div>
</Grid> </Grid>
</main> </main>
</div> </div>

View file

@ -1,28 +1,19 @@
import { useFormikContext } from 'formik' import { useFormikContext } from 'formik'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { Prompt } from 'react-router-dom'
const PROMPT_DEFAULT_MESSAGE = import useDirtyHandler from '../routing/dirtyHandler.js'
'You have unsaved changes on this page. Are you sure you want to leave?'
const PromptWhenDirty = ({ message = PROMPT_DEFAULT_MESSAGE }) => { const PromptWhenDirty = () => {
const setIsDirty = useDirtyHandler(state => state.setIsDirty)
const formik = useFormikContext() const formik = useFormikContext()
const hasChanges = formik.dirty && formik.submitCount === 0 const hasChanges = formik.dirty && formik.submitCount === 0
useEffect(() => { useEffect(() => {
if (hasChanges) { setIsDirty(hasChanges)
window.onbeforeunload = confirmExit
} else {
window.onbeforeunload = undefined
}
}, [hasChanges]) }, [hasChanges])
const confirmExit = () => { return <></>
return PROMPT_DEFAULT_MESSAGE
}
return <Prompt when={hasChanges} message={message} />
} }
export default PromptWhenDirty export default PromptWhenDirty

View file

@ -4,7 +4,7 @@ import Popper from '@mui/material/Popper'
import classnames from 'classnames' import classnames from 'classnames'
import * as R from 'ramda' import * as R from 'ramda'
import React, { memo, useState, useEffect, useRef } from 'react' import React, { memo, useState, useEffect, useRef } from 'react'
import { NavLink, useHistory } from 'react-router-dom' import { Link as WLink, useRoute, useLocation } from 'wouter'
import ActionButton from '../buttons/ActionButton' import ActionButton from '../buttons/ActionButton'
import { H4 } from '../typography' import { H4 } from '../typography'
import AddIconReverse from '../../styling/icons/button/add/white.svg?react' import AddIconReverse from '../../styling/icons/button/add/white.svg?react'
@ -23,6 +23,32 @@ const HAS_UNREAD = gql`
} }
` `
const Link = ({
setActive,
isParent,
className,
activeClassName,
item,
...props
}) => {
const [location] = useLocation()
const [isActive] = useRoute(props.to)
if (isActive) setActive(item)
const isParentActive = isParent && location.startsWith(props.to)
const classNames = classnames({
[className]: true,
[activeClassName]: isActive || isParentActive,
})
return (
<WLink {...props} asChild>
<a className={classNames}>{props.children}</a>
</WLink>
)
}
const Subheader = ({ item, user }) => { const Subheader = ({ item, user }) => {
const [prev, setPrev] = useState(null) const [prev, setPrev] = useState(null)
@ -35,17 +61,15 @@ const Subheader = ({ item, user }) => {
if (!R.includes(user.role, it.allowedRoles)) return <></> if (!R.includes(user.role, it.allowedRoles)) return <></>
return ( return (
<li key={idx} className={styles.subheaderLi}> <li key={idx} className={styles.subheaderLi}>
<NavLink <Link
to={{ pathname: it.route, state: { prev } }} to={it.route}
state={{ prev }}
className={styles.subheaderLink} className={styles.subheaderLink}
activeClassName={styles.activeSubheaderLink} activeClassName={styles.activeSubheaderLink}
isActive={match => { item={it.route}
if (!match) return false setActive={setPrev}>
setPrev(it.route)
return true
}}>
{it.label} {it.label}
</NavLink> </Link>
</li> </li>
) )
})} })}
@ -68,7 +92,7 @@ const Header = memo(({ tree, user }) => {
const { data, refetch, startPolling, stopPolling } = useQuery(HAS_UNREAD) const { data, refetch, startPolling, stopPolling } = useQuery(HAS_UNREAD)
const notifCenterButtonRef = useRef() const notifCenterButtonRef = useRef()
const popperRef = useRef() const popperRef = useRef()
const history = useHistory() const [, navigate] = useLocation()
useEffect(() => { useEffect(() => {
if (data?.hasUnreadNotifications) return setHasUnread(true) if (data?.hasUnreadNotifications) return setHasUnread(true)
@ -83,7 +107,7 @@ const Header = memo(({ tree, user }) => {
const onPaired = machine => { const onPaired = machine => {
setOpen(false) setOpen(false)
history.push('/maintenance/machine-status', { id: machine.deviceId }) navigate('/maintenance/machine-status', { state: { id: machine.deviceId } })
} }
// these inline styles prevent scroll bubbling: when the user reaches the bottom of the notifications list and keeps scrolling, // these inline styles prevent scroll bubbling: when the user reaches the bottom of the notifications list and keeps scrolling,
@ -114,7 +138,7 @@ const Header = memo(({ tree, user }) => {
<div <div
onClick={() => { onClick={() => {
setActive(false) setActive(false)
history.push('/dashboard') navigate('/dashboard')
}} }}
className={classnames(styles.logo, styles.logoLink)}> className={classnames(styles.logo, styles.logoLink)}>
<Logo /> <Logo />
@ -125,15 +149,13 @@ const Header = memo(({ tree, user }) => {
{tree.map((it, idx) => { {tree.map((it, idx) => {
if (!R.includes(user.role, it.allowedRoles)) return <></> if (!R.includes(user.role, it.allowedRoles)) return <></>
return ( return (
<NavLink <Link
isParent
key={idx} key={idx}
to={it.route || it.children[0].route} to={it.route || it.children[0].route}
isActive={match => { setActive={setActive}
if (!match) return false item={it}
setActive(it) className={styles.link}
return true
}}
className={classnames(styles.link)}
activeClassName={styles.activeLink}> activeClassName={styles.activeLink}>
<li className={styles.li}> <li className={styles.li}>
<span <span
@ -142,7 +164,7 @@ const Header = memo(({ tree, user }) => {
{it.label} {it.label}
</span> </span>
</li> </li>
</NavLink> </Link>
) )
})} })}
</ul> </ul>

View file

@ -1,11 +1,8 @@
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import { createRoot } from 'react-dom/client'
import App from './App' import App from './App'
ReactDOM.render( const container = document.getElementById('root')
<React.StrictMode> const root = createRoot(container)
<App /> root.render(<App />)
</React.StrictMode>,
document.getElementById('root'),
)

View file

@ -1,7 +1,7 @@
import { useMutation, useLazyQuery, gql } from '@apollo/client' import { useMutation, useLazyQuery, gql } from '@apollo/client'
import { Form, Formik } from 'formik' import { Form, Formik } from 'formik'
import React, { useContext, useState } from 'react' import React, { useContext, useState } from 'react'
import { useHistory } from 'react-router-dom' import { useLocation } from 'wouter'
import { TL1, P } from '../../components/typography' import { TL1, P } from '../../components/typography'
import AppContext from '../../AppContext' import AppContext from '../../AppContext'
@ -37,7 +37,7 @@ const GET_USER_DATA = gql`
` `
const Input2FAState = ({ state, dispatch }) => { const Input2FAState = ({ state, dispatch }) => {
const history = useHistory() const [, navigate] = useLocation()
const { setUserData } = useContext(AppContext) const { setUserData } = useContext(AppContext)
const [invalidToken, setInvalidToken] = useState(false) const [invalidToken, setInvalidToken] = useState(false)
@ -45,7 +45,7 @@ const Input2FAState = ({ state, dispatch }) => {
const [getUserData, { error: queryError }] = useLazyQuery(GET_USER_DATA, { const [getUserData, { error: queryError }] = useLazyQuery(GET_USER_DATA, {
onCompleted: ({ userData }) => { onCompleted: ({ userData }) => {
setUserData(userData) setUserData(userData)
history.push('/') navigate('/')
}, },
}) })

View file

@ -2,7 +2,7 @@ import { useMutation, useLazyQuery, gql } from '@apollo/client'
import { startAssertion } from '@simplewebauthn/browser' import { startAssertion } from '@simplewebauthn/browser'
import { Field, Form, Formik } from 'formik' import { Field, Form, Formik } from 'formik'
import React, { useState, useContext } from 'react' import React, { useState, useContext } from 'react'
import { useHistory } from 'react-router-dom' import { useLocation } from 'wouter'
import { H2, Label2, P } from '../../components/typography' import { H2, Label2, P } from '../../components/typography'
import * as Yup from 'yup' import * as Yup from 'yup'
@ -61,7 +61,7 @@ const InputFIDOState = ({ state, strategy }) => {
} }
` `
const history = useHistory() const [, navigate] = useLocation()
const { setUserData } = useContext(AppContext) const { setUserData } = useContext(AppContext)
const [localClientField, setLocalClientField] = useState('') const [localClientField, setLocalClientField] = useState('')
@ -125,7 +125,7 @@ const InputFIDOState = ({ state, strategy }) => {
const [getUserData, { error: queryError }] = useLazyQuery(GET_USER_DATA, { const [getUserData, { error: queryError }] = useLazyQuery(GET_USER_DATA, {
onCompleted: ({ userData }) => { onCompleted: ({ userData }) => {
setUserData(userData) setUserData(userData)
history.push('/') navigate('/')
}, },
}) })

View file

@ -2,7 +2,7 @@ import { useMutation, useLazyQuery, gql } from '@apollo/client'
import { startAssertion } from '@simplewebauthn/browser' import { startAssertion } from '@simplewebauthn/browser'
import { Field, Form, Formik } from 'formik' import { Field, Form, Formik } from 'formik'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { useHistory } from 'react-router-dom' import { useLocation } from 'wouter'
import { Label3, P } from '../../components/typography' import { Label3, P } from '../../components/typography'
import * as Yup from 'yup' import * as Yup from 'yup'
@ -67,7 +67,7 @@ const getErrorMsg = (formikErrors, formikTouched, mutationError) => {
} }
const LoginState = ({ dispatch, strategy }) => { const LoginState = ({ dispatch, strategy }) => {
const history = useHistory() const [, navigate] = useLocation()
const { setUserData } = useContext(AppContext) const { setUserData } = useContext(AppContext)
const [login, { error: loginMutationError }] = useMutation(LOGIN) const [login, { error: loginMutationError }] = useMutation(LOGIN)
@ -125,7 +125,7 @@ const LoginState = ({ dispatch, strategy }) => {
{ {
onCompleted: ({ userData }) => { onCompleted: ({ userData }) => {
setUserData(userData) setUserData(userData)
history.push('/') navigate('/')
}, },
}, },
) )

View file

@ -3,7 +3,7 @@ import Grid from '@mui/material/Grid'
import Paper from '@mui/material/Paper' import Paper from '@mui/material/Paper'
import { Field, Form, Formik } from 'formik' import { Field, Form, Formik } from 'formik'
import React, { useReducer } from 'react' import React, { useReducer } from 'react'
import { useLocation, useHistory } from 'react-router-dom' import { useLocation, useSearchParams } from 'wouter'
import { H2, Label3, P } from '../../components/typography' import { H2, Label3, P } from '../../components/typography'
import Logo from '../../styling/icons/menu/logo.svg?react' import Logo from '../../styling/icons/menu/logo.svg?react'
import * as Yup from 'yup' import * as Yup from 'yup'
@ -12,8 +12,6 @@ import { Button } from '../../components/buttons'
import { SecretInput } from '../../components/inputs/formik' import { SecretInput } from '../../components/inputs/formik'
import classes from './Authentication.module.css' import classes from './Authentication.module.css'
const QueryParams = () => new URLSearchParams(useLocation().search)
const VALIDATE_REGISTER_LINK = gql` const VALIDATE_REGISTER_LINK = gql`
query validateRegisterLink($token: String!) { query validateRegisterLink($token: String!) {
validateRegisterLink(token: $token) { validateRegisterLink(token: $token) {
@ -84,8 +82,9 @@ const getErrorMsg = (
} }
const Register = () => { const Register = () => {
const history = useHistory() const [, navigate] = useLocation()
const token = QueryParams().get('t') const [searchParams] = useSearchParams()
const token = searchParams.get('t')
const [state, dispatch] = useReducer(reducer, initialState) const [state, dispatch] = useReducer(reducer, initialState)
@ -118,7 +117,7 @@ const Register = () => {
const [register, { error: mutationError }] = useMutation(REGISTER, { const [register, { error: mutationError }] = useMutation(REGISTER, {
onCompleted: ({ register: success }) => { onCompleted: ({ register: success }) => {
if (success) history.push('/wizard', { fromAuthRegister: true }) if (success) navigate('/')
}, },
}) })

View file

@ -4,7 +4,7 @@ import Paper from '@mui/material/Paper'
import { Form, Formik } from 'formik' import { Form, Formik } from 'formik'
import { QRCodeSVG as QRCode } from 'qrcode.react' import { QRCodeSVG as QRCode } from 'qrcode.react'
import React, { useReducer, useState } from 'react' import React, { useReducer, useState } from 'react'
import { useLocation, useHistory } from 'react-router-dom' import { useLocation, useSearchParams } from 'wouter'
import { H2, Label2, Label3, P } from '../../components/typography' import { H2, Label2, Label3, P } from '../../components/typography'
import Logo from '../../styling/icons/menu/logo.svg?react' import Logo from '../../styling/icons/menu/logo.svg?react'
@ -43,9 +43,9 @@ const reducer = (state, action) => {
} }
const Reset2FA = () => { const Reset2FA = () => {
const history = useHistory() const [, navigate] = useLocation()
const QueryParams = () => new URLSearchParams(useLocation().search) const [searchParams] = useSearchParams()
const token = QueryParams().get('t') const token = searchParams.get('t')
const [isShowing, setShowing] = useState(false) const [isShowing, setShowing] = useState(false)
const [invalidToken, setInvalidToken] = useState(false) const [invalidToken, setInvalidToken] = useState(false)
@ -85,7 +85,7 @@ const Reset2FA = () => {
const [reset2FA, { error: mutationError }] = useMutation(RESET_2FA, { const [reset2FA, { error: mutationError }] = useMutation(RESET_2FA, {
onCompleted: ({ reset2FA: success }) => { onCompleted: ({ reset2FA: success }) => {
success ? history.push('/') : setInvalidToken(true) success ? navigate('/') : setInvalidToken(true)
}, },
}) })

View file

@ -3,7 +3,7 @@ import Grid from '@mui/material/Grid'
import Paper from '@mui/material/Paper' import Paper from '@mui/material/Paper'
import { Field, Form, Formik } from 'formik' import { Field, Form, Formik } from 'formik'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useLocation, useHistory } from 'react-router-dom' import { useLocation, useSearchParams } from 'wouter'
import { H2, Label3, P } from '../../components/typography' import { H2, Label3, P } from '../../components/typography'
import Logo from '../../styling/icons/menu/logo.svg?react' import Logo from '../../styling/icons/menu/logo.svg?react'
import * as Yup from 'yup' import * as Yup from 'yup'
@ -57,9 +57,9 @@ const getErrorMsg = (formikErrors, formikTouched, mutationError) => {
} }
const ResetPassword = () => { const ResetPassword = () => {
const history = useHistory() const [, navigate] = useLocation()
const QueryParams = () => new URLSearchParams(useLocation().search) const [searchParams] = useSearchParams()
const token = QueryParams().get('t') const token = searchParams.get('t')
const [userID, setUserID] = useState(null) const [userID, setUserID] = useState(null)
const [isLoading, setLoading] = useState(true) const [isLoading, setLoading] = useState(true)
const [wasSuccessful, setSuccess] = useState(false) const [wasSuccessful, setSuccess] = useState(false)
@ -83,7 +83,7 @@ const ResetPassword = () => {
const [resetPassword, { error }] = useMutation(RESET_PASSWORD, { const [resetPassword, { error }] = useMutation(RESET_PASSWORD, {
onCompleted: ({ resetPassword: success }) => { onCompleted: ({ resetPassword: success }) => {
if (success) history.push('/') if (success) navigate('/')
}, },
}) })

View file

@ -2,7 +2,7 @@ import { useMutation, useQuery, useLazyQuery, gql } from '@apollo/client'
import { Form, Formik } from 'formik' import { Form, Formik } from 'formik'
import { QRCodeSVG as QRCode } from 'qrcode.react' import { QRCodeSVG as QRCode } from 'qrcode.react'
import React, { useContext, useState } from 'react' import React, { useContext, useState } from 'react'
import { useHistory } from 'react-router-dom' import { useLocation } from 'wouter'
import { Label3, P } from '../../components/typography' import { Label3, P } from '../../components/typography'
import AppContext from '../../AppContext' import AppContext from '../../AppContext'
@ -48,7 +48,7 @@ const GET_USER_DATA = gql`
` `
const Setup2FAState = ({ state }) => { const Setup2FAState = ({ state }) => {
const history = useHistory() const [, navigate] = useLocation()
const { setUserData } = useContext(AppContext) const { setUserData } = useContext(AppContext)
const [secret, setSecret] = useState(null) const [secret, setSecret] = useState(null)
@ -85,7 +85,7 @@ const Setup2FAState = ({ state }) => {
const [getUserData] = useLazyQuery(GET_USER_DATA, { const [getUserData] = useLazyQuery(GET_USER_DATA, {
onCompleted: ({ userData }) => { onCompleted: ({ userData }) => {
setUserData(userData) setUserData(userData)
history.push('/') navigate('/')
}, },
}) })

View file

@ -4,7 +4,7 @@ import Switch from '@mui/material/Switch'
import NavigateNextIcon from '@mui/icons-material/NavigateNext' import NavigateNextIcon from '@mui/icons-material/NavigateNext'
import * as R from 'ramda' import * as R from 'ramda'
import React, { memo, useState } from 'react' import React, { memo, useState } from 'react'
import { useHistory, useParams } from 'react-router-dom' import { useLocation, useParams } from 'wouter'
import { Label1, Label2 } from '../../components/typography' import { Label1, Label2 } from '../../components/typography'
import AuthorizeReversedIcon from '../../styling/icons/button/authorize/white.svg?react' import AuthorizeReversedIcon from '../../styling/icons/button/authorize/white.svg?react'
import AuthorizeIcon from '../../styling/icons/button/authorize/zodiac.svg?react' import AuthorizeIcon from '../../styling/icons/button/authorize/zodiac.svg?react'
@ -284,7 +284,7 @@ const CHECK_AGAINST_SANCTIONS = gql`
` `
const CustomerProfile = memo(() => { const CustomerProfile = memo(() => {
const history = useHistory() const [, navigate] = useLocation()
const [showCompliance, setShowCompliance] = useState(false) const [showCompliance, setShowCompliance] = useState(false)
const [wizard, setWizard] = useState(false) const [wizard, setWizard] = useState(false)
@ -515,7 +515,7 @@ const CustomerProfile = memo(() => {
<Label1 <Label1
noMargin noMargin
className="cursor-pointer text-comet" className="cursor-pointer text-comet"
onClick={() => history.push('/compliance/customers')}> onClick={() => navigate('/compliance/customers')}>
Customers Customers
</Label1> </Label1>
<Label2 noMargin className="cursor-pointer text-comet"> <Label2 noMargin className="cursor-pointer text-comet">

View file

@ -1,7 +1,7 @@
import { useQuery, useMutation, gql } from '@apollo/client' import { useQuery, useMutation, gql } from '@apollo/client'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useHistory } from 'react-router-dom' import { useLocation } from 'wouter'
import SearchBox from '../../components/SearchBox' import SearchBox from '../../components/SearchBox'
import SearchFilter from '../../components/SearchFilter' import SearchFilter from '../../components/SearchFilter'
import TitleSection from '../../components/layout/TitleSection' import TitleSection from '../../components/layout/TitleSection'
@ -95,10 +95,10 @@ const getFiltersObj = filters =>
R.reduce((s, f) => ({ ...s, [f.type]: f.value }), {}, filters) R.reduce((s, f) => ({ ...s, [f.type]: f.value }), {}, filters)
const Customers = () => { const Customers = () => {
const history = useHistory() const [, navigate] = useLocation()
const handleCustomerClicked = customer => const handleCustomerClicked = customer =>
history.push(`/compliance/customer/${customer.id}`) navigate(`/compliance/customer/${customer.id}`)
const [filteredCustomers, setFilteredCustomers] = useState([]) const [filteredCustomers, setFilteredCustomers] = useState([])
const [variables, setVariables] = useState({}) const [variables, setVariables] = useState({})

View file

@ -2,7 +2,7 @@ import List from '@mui/material/List'
import ListItem from '@mui/material/ListItem' import ListItem from '@mui/material/ListItem'
import * as R from 'ramda' import * as R from 'ramda'
import React from 'react' import React from 'react'
import { useHistory } from 'react-router-dom' import { useLocation } from 'wouter'
import { P } from '../../../components/typography/index' import { P } from '../../../components/typography/index'
import Wrench from '../../../styling/icons/action/wrench/zodiac.svg?react' import Wrench from '../../../styling/icons/action/wrench/zodiac.svg?react'
import CashBoxEmpty from '../../../styling/icons/cassettes/cashbox-empty.svg?react' import CashBoxEmpty from '../../../styling/icons/cassettes/cashbox-empty.svg?react'
@ -23,7 +23,7 @@ const links = {
} }
const AlertsTable = ({ numToRender, alerts, machines }) => { const AlertsTable = ({ numToRender, alerts, machines }) => {
const history = useHistory() const [, navigate] = useLocation()
const alertsToRender = R.slice(0, numToRender, alerts) const alertsToRender = R.slice(0, numToRender, alerts)
const alertMessage = alert => { const alertMessage = alert => {
@ -45,7 +45,7 @@ const AlertsTable = ({ numToRender, alerts, machines }) => {
<P className="my-2">{alertMessage(alert)}</P> <P className="my-2">{alertMessage(alert)}</P>
<AlertLinkIcon <AlertLinkIcon
className="ml-auto cursor-pointer" className="ml-auto cursor-pointer"
onClick={() => history.push(links[alert.type] || '/dashboard')} onClick={() => navigate(links[alert.type] || '/dashboard')}
/> />
</ListItem> </ListItem>
) )

View file

@ -1,7 +1,7 @@
import { useQuery, gql } from '@apollo/client' import { useQuery, gql } from '@apollo/client'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useHistory } from 'react-router-dom' import { useLocation } from 'wouter'
import TitleSection from '../../components/layout/TitleSection' import TitleSection from '../../components/layout/TitleSection'
import { H1, Info2, TL2, Label1 } from '../../components/typography' import { H1, Info2, TL2, Label1 } from '../../components/typography'
import TxInIcon from '../../styling/icons/direction/cash-in.svg?react' import TxInIcon from '../../styling/icons/direction/cash-in.svg?react'
@ -26,14 +26,14 @@ const GET_DATA = gql`
` `
const Dashboard = () => { const Dashboard = () => {
const history = useHistory() const [, navigate] = useLocation()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const { data, loading } = useQuery(GET_DATA) const { data, loading } = useQuery(GET_DATA)
const onPaired = machine => { const onPaired = machine => {
setOpen(false) setOpen(false)
history.push('/maintenance/machine-status', { id: machine.deviceId }) navigate('/maintenance/machine-status', { state: { id: machine.deviceId } })
} }
return !loading ? ( return !loading ? (

View file

@ -8,7 +8,7 @@ import TableHead from '@mui/material/TableHead'
import TableRow from '@mui/material/TableRow' import TableRow from '@mui/material/TableRow'
import * as R from 'ramda' import * as R from 'ramda'
import React from 'react' import React from 'react'
import { useHistory } from 'react-router-dom' import { useLocation } from 'wouter'
import { Status } from '../../../components/Status' import { Status } from '../../../components/Status'
import { Label2, TL2 } from '../../../components/typography' import { Label2, TL2 } from '../../../components/typography'
import TxOutIcon from '../../../styling/icons/direction/cash-out.svg?react' import TxOutIcon from '../../../styling/icons/direction/cash-out.svg?react'
@ -39,7 +39,7 @@ const HeaderCell = styled(TableCell)({
}) })
const MachinesTable = ({ machines = [], numToRender }) => { const MachinesTable = ({ machines = [], numToRender }) => {
const history = useHistory() const [, navigate] = useLocation()
const { data } = useQuery(GET_CONFIG) const { data } = useQuery(GET_CONFIG)
const fillingPercentageSettings = fromNamespace( const fillingPercentageSettings = fromNamespace(
@ -65,8 +65,10 @@ const MachinesTable = ({ machines = [], numToRender }) => {
} }
const redirect = ({ name, deviceId }) => { const redirect = ({ name, deviceId }) => {
return history.push(`/machines/${deviceId}`, { return navigate(`/machines/${deviceId}`, {
state: {
selectedMachine: name, selectedMachine: name,
},
}) })
} }

View file

@ -3,7 +3,7 @@ import Breadcrumbs from '@mui/material/Breadcrumbs'
import NavigateNextIcon from '@mui/icons-material/NavigateNext' import NavigateNextIcon from '@mui/icons-material/NavigateNext'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { Link, useLocation, useHistory } from 'react-router-dom' import { Link, useLocation } from 'wouter'
import { TL1, TL2, Label3 } from '../../components/typography' import { TL1, TL2, Label3 } from '../../components/typography'
import Cassettes from './MachineComponents/Cassettes' import Cassettes from './MachineComponents/Cassettes'
@ -61,17 +61,13 @@ const GET_INFO = gql`
const getMachineID = path => path.slice(path.lastIndexOf('/') + 1) const getMachineID = path => path.slice(path.lastIndexOf('/') + 1)
const MachineRoute = () => { const MachineRoute = () => {
const location = useLocation() const [location, navigate] = useLocation()
const history = useHistory()
const id = getMachineID(location.pathname)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const id = getMachineID(location)
const { data, refetch } = useQuery(GET_INFO, { const { data, refetch } = useQuery(GET_INFO, {
onCompleted: data => { onCompleted: data => {
if (data.machine === null) if (data.machine === null) return navigate('/maintenance/machine-status')
return history.push('/maintenance/machine-status')
setLoading(false) setLoading(false)
}, },
@ -85,7 +81,7 @@ const MachineRoute = () => {
}) })
const reload = () => { const reload = () => {
return history.push(location.pathname) return navigate(location)
} }
return ( return (

View file

@ -2,7 +2,7 @@ import { useQuery, gql } from '@apollo/client'
import { formatDistance } from 'date-fns' import { formatDistance } from 'date-fns'
import * as R from 'ramda' import * as R from 'ramda'
import React from 'react' import React from 'react'
import { useHistory, useLocation } from 'react-router-dom' import { useLocation } from 'wouter'
import { MainStatus } from '../../components/Status' import { MainStatus } from '../../components/Status'
import Title from '../../components/Title' import Title from '../../components/Title'
import DataTable from '../../components/tables/DataTable' import DataTable from '../../components/tables/DataTable'
@ -55,9 +55,8 @@ const GET_DATA = gql`
` `
const MachineStatus = () => { const MachineStatus = () => {
const history = useHistory() const [, navigate] = useLocation()
const { state } = useLocation() const addedMachineId = history.state?.id
const addedMachineId = state?.id
const { const {
data: machinesResponse, data: machinesResponse,
refetch, refetch,
@ -77,7 +76,7 @@ const MachineStatus = () => {
{m.name} {m.name}
<div <div
onClick={() => { onClick={() => {
history.push(`/machines/${m.deviceId}`) navigate(`/machines/${m.deviceId}`)
}}> }}>
<MachineRedirectIcon /> <MachineRedirectIcon />
</div> </div>

View file

@ -3,7 +3,7 @@ import { toUnit, formatCryptoAddress } from '@lamassu/coins/lightUtils'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useHistory } from 'react-router-dom' import { useLocation } from 'wouter'
import LogsDowloaderPopover from '../../components/LogsDownloaderPopper' import LogsDowloaderPopover from '../../components/LogsDownloaderPopper'
import SearchBox from '../../components/SearchBox' import SearchBox from '../../components/SearchBox'
import SearchFilter from '../../components/SearchFilter' import SearchFilter from '../../components/SearchFilter'
@ -134,7 +134,7 @@ const getFiltersObj = filters =>
R.reduce((s, f) => ({ ...s, [f.type]: f.value }), {}, filters) R.reduce((s, f) => ({ ...s, [f.type]: f.value }), {}, filters)
const Transactions = () => { const Transactions = () => {
const history = useHistory() const [, navigate] = useLocation()
const [filters, setFilters] = useState([]) const [filters, setFilters] = useState([])
const { data: filtersResponse, loading: filtersLoading } = useQuery( const { data: filtersResponse, loading: filtersLoading } = useQuery(
@ -160,7 +160,7 @@ const Transactions = () => {
const timezone = R.path(['config', 'locale_timezone'], configResponse) const timezone = R.path(['config', 'locale_timezone'], configResponse)
const redirect = customerId => { const redirect = customerId => {
return history.push(`/compliance/customer/${customerId}`) return navigate(`/compliance/customer/${customerId}`)
} }
const elements = [ const elements = [

View file

@ -3,7 +3,7 @@ import Dialog from '@mui/material/Dialog'
import DialogContent from '@mui/material/DialogContent' import DialogContent from '@mui/material/DialogContent'
import classnames from 'classnames' import classnames from 'classnames'
import React, { useState, useContext } from 'react' import React, { useState, useContext } from 'react'
import { useHistory } from 'react-router-dom' import { useLocation } from 'wouter'
import { getWizardStep, STEPS } from './helper' import { getWizardStep, STEPS } from './helper'
import AppContext from '../../AppContext' import AppContext from '../../AppContext'
@ -23,7 +23,7 @@ const GET_DATA = gql`
const Wizard = () => { const Wizard = () => {
const { data, loading } = useQuery(GET_DATA) const { data, loading } = useQuery(GET_DATA)
const history = useHistory() const [, navigate] = useLocation()
const { setWizardTested } = useContext(AppContext) const { setWizardTested } = useContext(AppContext)
const [step, setStep] = useState(0) const [step, setStep] = useState(0)
@ -37,12 +37,9 @@ const Wizard = () => {
const wizardStep = getWizardStep(data?.config, data?.cryptoCurrencies) const wizardStep = getWizardStep(data?.config, data?.cryptoCurrencies)
const shouldGoBack =
history.length && !history.location.state?.fromAuthRegister
if (wizardStep === 0) { if (wizardStep === 0) {
setWizardTested(true) setWizardTested(true)
shouldGoBack ? history.goBack() : history.push('/') return <></>
} }
const isWelcome = step === 0 const isWelcome = step === 0
@ -54,7 +51,9 @@ const Wizard = () => {
const doContinue = () => { const doContinue = () => {
if (step >= STEPS.length - 1) { if (step >= STEPS.length - 1) {
setOpen(false) setOpen(false)
history.push('/') setWizardTested(true)
navigate('/')
return
} }
const nextStep = step === 0 && wizardStep ? wizardStep : step + 1 const nextStep = step === 0 && wizardStep ? wizardStep : step + 1

View file

@ -1,5 +1,5 @@
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { Route, Redirect } from 'react-router-dom' import { Route, Redirect } from 'wouter'
import AppContext from '../AppContext' import AppContext from '../AppContext'

View file

@ -1,24 +1,17 @@
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { Route, Redirect } from 'react-router-dom' import { Route, Redirect } from 'wouter'
import AppContext from '../AppContext' import AppContext from '../AppContext'
import { isLoggedIn } from './utils' import { isLoggedIn } from './utils'
const PublicRoute = ({ component: Component, restricted, ...rest }) => { const PublicRoute = ({ restricted, ...rest }) => {
const { userData } = useContext(AppContext) const { userData } = useContext(AppContext)
return ( return isLoggedIn(userData) && restricted ? (
<Route
{...rest}
render={props =>
isLoggedIn(userData) && restricted ? (
<Redirect to="/" /> <Redirect to="/" />
) : ( ) : (
<Component {...props} /> <Route {...rest} />
)
}
/>
) )
} }

View file

@ -0,0 +1,8 @@
import { create } from 'zustand'
const useDirtyHandler = create(set => ({
isDirty: false,
setIsDirty: it => set({ isDirty: it }),
}))
export default useDirtyHandler

View file

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { Redirect } from 'react-router-dom' import { Redirect } from 'wouter'
import Funding from '../pages/Funding/Funding.jsx' import Funding from '../pages/Funding/Funding.jsx'
import IndividualDiscounts from '../pages/LoyaltyPanel/IndividualDiscounts' import IndividualDiscounts from '../pages/LoyaltyPanel/IndividualDiscounts'
import PromoCodes from '../pages/LoyaltyPanel/PromoCodes' import PromoCodes from '../pages/LoyaltyPanel/PromoCodes'

View file

@ -2,13 +2,7 @@ import Fade from '@mui/material/Fade'
import Slide from '@mui/material/Slide' import Slide from '@mui/material/Slide'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { import { Redirect, Switch, useLocation } from 'wouter'
matchPath,
Redirect,
Switch,
useHistory,
useLocation,
} from 'react-router-dom'
import Login from '../pages/Authentication/Login' import Login from '../pages/Authentication/Login'
import Register from '../pages/Authentication/Register' import Register from '../pages/Authentication/Register'
import Reset2FA from '../pages/Authentication/Reset2FA' import Reset2FA from '../pages/Authentication/Reset2FA'
@ -17,7 +11,6 @@ import ResetPassword from '../pages/Authentication/ResetPassword'
import AppContext from '../AppContext' import AppContext from '../AppContext'
import Dashboard from '../pages/Dashboard' import Dashboard from '../pages/Dashboard'
import Machines from '../pages/Machines' import Machines from '../pages/Machines'
import Wizard from '../pages/Wizard'
import PrivateRoute from './PrivateRoute' import PrivateRoute from './PrivateRoute'
import PublicRoute from './PublicRoute' import PublicRoute from './PublicRoute'
@ -57,27 +50,12 @@ const getParent = route =>
)(flattened) )(flattened)
const Routes = () => { const Routes = () => {
const history = useHistory() const [location] = useLocation()
const location = useLocation() const { userData } = useContext(AppContext)
const { wizardTested, userData } = useContext(AppContext)
const dontTriggerPages = [
'/404',
'/register',
'/wizard',
'/login',
'/register',
'/resetpassword',
'/reset2fa',
]
if (!wizardTested && !R.contains(location.pathname)(dontTriggerPages)) {
history.push('/wizard')
return null
}
const getFilteredRoutes = () => { const getFilteredRoutes = () => {
if (!userData) return [] // return all to prevent the user from being stuck at a 404 page
if (!userData) return flattened
return flattened.filter(value => { return flattened.filter(value => {
const keys = value.allowedRoles const keys = value.allowedRoles
@ -85,14 +63,14 @@ const Routes = () => {
}) })
} }
const Transition = location.state ? Slide : Fade const Transition = history.state ? Slide : Fade
const transitionProps = const transitionProps =
Transition === Slide Transition === Slide
? { ? {
direction: direction:
R.findIndex(R.propEq('route', location.state.prev))(leafRoutes) > R.findIndex(R.propEq('route', history.state.prev))(leafRoutes) >
R.findIndex(R.propEq('route', location.pathname))(leafRoutes) R.findIndex(R.propEq('route', location))(leafRoutes)
? 'right' ? 'right'
: 'left', : 'left',
} }
@ -101,9 +79,9 @@ const Routes = () => {
return ( return (
<Switch> <Switch>
<PrivateRoute exact path="/"> <PrivateRoute exact path="/">
<Redirect to={{ pathname: '/dashboard' }} /> <Redirect to="/dashboard" />
</PrivateRoute> </PrivateRoute>
<PrivateRoute path={'/dashboard'}> <PrivateRoute path="/dashboard">
<Transition <Transition
className={wrapperClasses} className={wrapperClasses}
{...transitionProps} {...transitionProps}
@ -115,31 +93,30 @@ const Routes = () => {
</div> </div>
</Transition> </Transition>
</PrivateRoute> </PrivateRoute>
<PrivateRoute path="/machines" component={Machines} /> <PrivateRoute path="/machines">
<PrivateRoute path="/wizard" component={Wizard} /> <Machines />
</PrivateRoute>
<PublicRoute path="/register" component={Register} /> <PublicRoute path="/register" component={Register} />
<PublicRoute path="/login" restricted component={Login} />
<PublicRoute path="/resetpassword" component={ResetPassword} /> <PublicRoute path="/resetpassword" component={ResetPassword} />
<PublicRoute path="/reset2fa" component={Reset2FA} /> <PublicRoute path="/reset2fa" component={Reset2FA} />
<PublicRoute path="/login" restricted component={Login} />
{getFilteredRoutes().map(({ route, component: Page, key }) => ( {getFilteredRoutes().map(({ route, component: Page, key }) => (
<PrivateRoute path={route} key={key}> <PrivateRoute path={route} key={key}>
<Transition <Transition
className={wrapperClasses} className={wrapperClasses}
{...transitionProps} {...transitionProps}
in={!!matchPath(location.pathname, { path: route })} in={location === route}
mountOnEnter mountOnEnter
unmountOnExit> unmountOnExit>
<div className={wrapperClasses}> <div className={wrapperClasses}>
<PrivateRoute path={route} key={key}>
<Page name={key} /> <Page name={key} />
</PrivateRoute>
</div> </div>
</Transition> </Transition>
</PrivateRoute> </PrivateRoute>
))} ))}
<PublicRoute path="/404" /> <PublicRoute path="/404" />
<PublicRoute path="*"> <PublicRoute path="*">
<Redirect to={{ pathname: '/404' }} /> <Redirect to="/404" />
</PublicRoute> </PublicRoute>
</Switch> </Switch>
) )

View file

@ -0,0 +1,34 @@
import useDirtyHandler from './dirtyHandler.js'
import { useEffect, useRef } from 'react'
import { useBrowserLocation } from 'wouter/use-browser-location'
const PROMPT_DEFAULT_MESSAGE =
'You have unsaved changes on this page. Are you sure you want to leave?'
const useLocationWithConfirmation = () => {
const setIsDirty = useDirtyHandler(state => state.setIsDirty)
const isDirtyRef = useRef(useDirtyHandler.getState().isDirty)
useEffect(
() =>
useDirtyHandler.subscribe(state => (isDirtyRef.current = state.isDirty)),
[],
)
const [location, setLocation] = useBrowserLocation()
return [
location,
newLocation => {
let perfomNavigation = true
if (isDirtyRef.current) {
perfomNavigation = window.confirm(PROMPT_DEFAULT_MESSAGE)
}
if (perfomNavigation) {
setLocation(newLocation)
setIsDirty(false)
}
},
]
}
export default useLocationWithConfirmation

View file

@ -7,7 +7,7 @@ import {
import { onError } from '@apollo/client/link/error' import { onError } from '@apollo/client/link/error'
import createUploadLink from 'apollo-upload-client/createUploadLink.mjs' import createUploadLink from 'apollo-upload-client/createUploadLink.mjs'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { useHistory, useLocation } from 'react-router-dom' import { useLocation } from 'wouter'
import AppContext from '../AppContext' import AppContext from '../AppContext'
@ -16,7 +16,7 @@ const uploadLink = createUploadLink({
uri: `/graphql`, uri: `/graphql`,
}) })
const getClient = (history, location, getUserData, setUserData, setRole) => const getClient = (navigate, location, getUserData, setUserData, setRole) =>
new ApolloClient({ new ApolloClient({
link: ApolloLink.from([ link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => { onError(({ graphQLErrors, networkError }) => {
@ -24,7 +24,7 @@ const getClient = (history, location, getUserData, setUserData, setRole) =>
graphQLErrors.forEach(({ message, locations, path, extensions }) => { graphQLErrors.forEach(({ message, locations, path, extensions }) => {
if (extensions?.code === 'UNAUTHENTICATED') { if (extensions?.code === 'UNAUTHENTICATED') {
setUserData(null) setUserData(null)
if (location.pathname !== '/login') history.push('/login') if (location !== '/login') navigate('/login')
} }
console.log( console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`, `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
@ -66,11 +66,10 @@ const getClient = (history, location, getUserData, setUserData, setRole) =>
}) })
const Provider = ({ children }) => { const Provider = ({ children }) => {
const history = useHistory() const [location, navigate] = useLocation()
const location = useLocation()
const { userData, setUserData, setRole } = useContext(AppContext) const { userData, setUserData, setRole } = useContext(AppContext)
const client = getClient( const client = getClient(
history, navigate,
location, location,
() => userData, () => userData,
setUserData, setUserData,