added LockNavigationContextProvider.tsx

This commit is contained in:
Myzel394 2022-10-29 12:45:02 +02:00
parent 04ede26ac6
commit ca9eb78330
7 changed files with 195 additions and 40 deletions

View File

@ -0,0 +1,27 @@
import {FormikContextType} from "formik"
import {useContext, useEffect} from "react"
import LockNavigationContext from "./LockNavigationContext"
export interface LockNavigationContextProviderProps {
formik: FormikContextType<any>
}
export default function FormikAutoLockNavigation({
formik,
}: LockNavigationContextProviderProps): null {
const {lock, release} = useContext(LockNavigationContext)
const valuesStringified = JSON.stringify(formik.values)
const initialValuesStringified = JSON.stringify(formik.initialValues)
useEffect(() => {
if (valuesStringified !== initialValuesStringified) {
lock()
} else {
release()
}
}, [lock, release, valuesStringified, initialValuesStringified])
return null
}

View File

@ -0,0 +1,27 @@
import {createContext} from "react"
export interface LockNavigationContextType {
isLocked: boolean
lock: () => void
release: () => void
navigate: (path: string) => void
handleAnchorClick: (event: React.MouseEvent<HTMLAnchorElement>) => void
}
const LockNavigationContext = createContext<LockNavigationContextType>({
isLocked: false,
lock: () => {
throw new Error("lock() not implemented")
},
release: () => {
throw new Error("release() not implemented")
},
navigate: () => {
throw new Error("navigate() not implemented")
},
handleAnchorClick: () => {
throw new Error("handleAnchorClick() not implemented")
},
})
export default LockNavigationContext

View File

@ -0,0 +1,91 @@
import {TiCancel} from "react-icons/ti"
import {ReactNode, useMemo, useState} from "react"
import {useNavigate} from "react-router-dom"
import {MdLogout} from "react-icons/md"
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from "@mui/material"
import LockNavigationContext, {
LockNavigationContextType,
} from "./LockNavigationContext"
export interface LockNavigationContextProviderProps {
children: ReactNode
}
export default function LockNavigationContextProvider({
children,
}: LockNavigationContextProviderProps): JSX.Element {
const navigate = useNavigate()
const [isLocked, setIsLocked] = useState<boolean>(false)
const [nextPath, setNextPath] = useState<string | null>(null)
const showDialog = Boolean(nextPath)
const value = useMemo(
(): LockNavigationContextType => ({
isLocked,
navigate: (path: string) => {
if (isLocked) {
setNextPath(path)
} else {
setNextPath(null)
navigate(path)
}
},
handleAnchorClick: (event: React.MouseEvent<HTMLAnchorElement>) => {
if (isLocked) {
event.preventDefault()
setNextPath(event.currentTarget.href)
}
},
lock: () => setIsLocked(true),
release: () => setIsLocked(false),
}),
[isLocked],
)
return (
<>
<LockNavigationContext.Provider value={value}>
{children}
</LockNavigationContext.Provider>
<Dialog open={showDialog} onClose={() => setNextPath(null)}>
<DialogTitle>Are you sure you want to go?</DialogTitle>
<DialogContent>
<DialogContentText>
You have unsaved changes. If you leave this page, your
changes will be lost.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
startIcon={<TiCancel />}
onClick={() => setNextPath(null)}
>
Cancel
</Button>
<Button
startIcon={<MdLogout />}
onClick={() => {
const path = new URL(nextPath as string).pathname
setNextPath(null)
setIsLocked(false)
navigate(path)
}}
>
Leave
</Button>
</DialogActions>
</Dialog>
</>
)
}

View File

@ -20,3 +20,5 @@ export * from "./ErrorLoadingDataMessage"
export {default as ErrorLoadingDataMessage} from "./ErrorLoadingDataMessage"
export * from "./DecryptReport"
export {default as DecryptReport} from "./DecryptReport"
export * from "./SimplePage"
export {default as SimplePage} from "./SimplePage"

View File

@ -20,6 +20,7 @@ import {
import {UpdateAliasData, updateAlias} from "~/apis"
import {ErrorSnack, SuccessSnack} from "~/components"
import {parseFastAPIError} from "~/utils"
import FormikAutoLockNavigation from "~/LockNavigationContext/FormikAutoLockNavigation"
import SelectField from "~/route-widgets/SettingsRoute/SelectField"
export interface AliasPreferencesFormProps {
@ -171,6 +172,7 @@ export default function AliasPreferencesForm({
</Grid>
</Grid>
</form>
<FormikAutoLockNavigation formik={formik} />
<ErrorSnack message={formik.errors.detail} />
<SuccessSnack
message={isSuccess && "Updated Alias successfully!"}

View File

@ -1,4 +1,4 @@
import {ReactElement} from "react"
import {ReactElement, useContext} from "react"
import {BiStats} from "react-icons/bi"
import {MdSettings} from "react-icons/md"
import {FaMask} from "react-icons/fa"
@ -7,6 +7,7 @@ import {Link as RouterLink, useLocation} from "react-router-dom"
import {Button} from "@mui/material"
import {mdiTextBoxMultiple} from "@mdi/js/commonjs/mdi"
import Icon from "@mdi/react"
import LockNavigationContext from "~/LockNavigationContext/LockNavigationContext"
export enum NavigationSection {
Overview,
@ -43,6 +44,7 @@ const PATH_SECTION_MAP: Record<string, NavigationSection> = {
export default function NavigationButton({
section,
}: NavigationButtonProps): ReactElement {
const {handleAnchorClick} = useContext(LockNavigationContext)
const location = useLocation()
const currentSection = PATH_SECTION_MAP[location.pathname.split("/")[1]]
@ -61,6 +63,7 @@ export default function NavigationButton({
path => PATH_SECTION_MAP[path] === section,
) ?? "/"
}
onClick={handleAnchorClick}
>
{text}
</Button>

View File

@ -4,6 +4,7 @@ import {Outlet} from "react-router-dom"
import {Box, Grid, List, ListItem, Paper, useTheme} from "@mui/material"
import {useUser} from "~/hooks"
import LockNavigationContextProvider from "~/LockNavigationContext/LockNavigationContextProvider"
import NavigationButton, {
NavigationSection,
} from "~/route-widgets/AuthenticateRoute/NavigationButton"
@ -18,55 +19,57 @@ export default function AuthenticatedRoute(): ReactElement {
useUser()
return (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
height="100vh"
>
<LockNavigationContextProvider>
<Box
display="flex"
maxWidth="90vw"
width="100%"
justifyContent="center"
flexDirection="column"
alignItems="center"
justifyContent="center"
height="100vh"
>
<Grid
maxWidth="md"
container
justifyContent="space-between"
<Box
display="flex"
maxWidth="90vw"
width="100%"
justifyContent="center"
alignItems="center"
>
<Grid item xs={12} sm={4} md={2}>
<Box
bgcolor={theme.palette.background.paper}
component="nav"
>
<List>
{sections.map(key => (
<ListItem key={key}>
<NavigationButton
section={NavigationSection[key]}
/>
</ListItem>
))}
</List>
</Box>
</Grid>
<Grid item xs={12} sm={8} md={10}>
<Paper>
<Grid
maxWidth="md"
container
justifyContent="space-between"
alignItems="center"
>
<Grid item xs={12} sm={4} md={2}>
<Box
maxHeight="80vh"
sx={{overflowY: "auto"}}
padding={4}
bgcolor={theme.palette.background.paper}
component="nav"
>
<Outlet />
<List>
{sections.map(key => (
<ListItem key={key}>
<NavigationButton
section={NavigationSection[key]}
/>
</ListItem>
))}
</List>
</Box>
</Paper>
</Grid>
<Grid item xs={12} sm={8} md={10}>
<Paper>
<Box
maxHeight="80vh"
sx={{overflowY: "auto"}}
padding={4}
>
<Outlet />
</Box>
</Paper>
</Grid>
</Grid>
</Grid>
</Box>
</Box>
</Box>
</LockNavigationContextProvider>
)
}