forked from ddrilling/asb_cloud_front
76 lines
2.9 KiB
TypeScript
76 lines
2.9 KiB
TypeScript
import { join } from 'path'
|
||
import { Location } from 'history'
|
||
import { Children, cloneElement, memo, ReactElement, ReactNode, useCallback, useContext, useMemo } from 'react'
|
||
import { Redirect, Route, Switch, SwitchProps, useLocation } from 'react-router-dom'
|
||
|
||
import { RootPathContext } from '@asb/context'
|
||
import { isURLAvailable } from '@utils/permissions'
|
||
import { getUserId } from '@utils/storage'
|
||
|
||
|
||
export type PrivateSwitchProps = SwitchProps & {
|
||
root?: string
|
||
redirect?: (location?: Location<unknown>) => ReactNode
|
||
elseRedirect?: string | string[]
|
||
}
|
||
|
||
const getDefaultRedirectPath = () => getUserId() ? '/access_denied' : '/login'
|
||
|
||
export const defaultRedirect = (location?: Location<unknown>) => (
|
||
<Redirect to={{ pathname: getDefaultRedirectPath(), state: { from: location?.pathname } }} />
|
||
)
|
||
|
||
export const PrivateSwitch = memo<PrivateSwitchProps>(({ root, elseRedirect, redirect = defaultRedirect, children }) => {
|
||
const rootContext = useContext(RootPathContext)
|
||
const rootPath = useMemo(() => root ?? rootContext ?? '', [root, rootContext])
|
||
|
||
const location = useLocation()
|
||
|
||
const toAbsolute = useCallback((path: string) => path.startsWith('/') ? path : join(rootPath, path), [rootPath])
|
||
|
||
const items = useMemo(() => Children.toArray(children).map((child) => {
|
||
const element = child as ReactElement
|
||
let key = element.key?.toString()
|
||
if (!key) return null
|
||
key = key.slice(key.lastIndexOf('$') + 1).replaceAll('=2', ':')
|
||
// Ключ автоматический преобразуется в "(.+)\$ключ"
|
||
// Все ":" в ключе заменяются на "=2"
|
||
// TODO: улучшить метод нормализации ключа
|
||
const path = toAbsolute(key)
|
||
return (
|
||
<Route
|
||
key={key}
|
||
path={path}
|
||
render={({ location }) => isURLAvailable(path) ? cloneElement(element) : redirect(location)}
|
||
/>
|
||
)
|
||
}), [children, redirect, toAbsolute])
|
||
|
||
const defaultRoute = useMemo(() => {
|
||
if (!elseRedirect) {
|
||
const path = items.map((elm) => elm?.props.path).find((path) => path && isURLAvailable(path))
|
||
if (path) return path
|
||
} else if (Array.isArray(elseRedirect)) {
|
||
const path = elseRedirect.find((path) => {
|
||
if (!path) return false
|
||
return isURLAvailable(toAbsolute(path))
|
||
})
|
||
if (path) return toAbsolute(path)
|
||
} else if(elseRedirect && isURLAvailable(toAbsolute(elseRedirect))) {
|
||
return toAbsolute(elseRedirect)
|
||
}
|
||
return getDefaultRedirectPath()
|
||
}, [items, elseRedirect, toAbsolute])
|
||
|
||
return (
|
||
<Switch>
|
||
{items}
|
||
<Route path={'/'}>
|
||
<Redirect to={{ pathname: defaultRoute, state: { from: location.pathname } }} />
|
||
</Route>
|
||
</Switch>
|
||
)
|
||
})
|
||
|
||
export default PrivateSwitch
|