mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-19 15:55:26 +02:00
added LockNavigationContextProvider.tsx
This commit is contained in:
parent
04ede26ac6
commit
ca9eb78330
27
src/LockNavigationContext/FormikAutoLockNavigation.tsx
Normal file
27
src/LockNavigationContext/FormikAutoLockNavigation.tsx
Normal 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
|
||||
}
|
27
src/LockNavigationContext/LockNavigationContext.ts
Normal file
27
src/LockNavigationContext/LockNavigationContext.ts
Normal 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
|
91
src/LockNavigationContext/LockNavigationContextProvider.tsx
Normal file
91
src/LockNavigationContext/LockNavigationContextProvider.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
@ -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"
|
||||
|
@ -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!"}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user