comty/packages/app/src/router.jsx

218 lines
6.7 KiB
JavaScript
Executable File

import React from "react"
import { Route, Routes, useLocation, useNavigate, useParams } from "react-router-dom"
import { Skeleton, Button, Result } from "antd"
import config from "config"
import loadable from "@loadable/component"
import routesDeclaration from "schemas/routes"
const DefaultNotFoundRender = () => {
return <div>Not found</div>
}
const DefaultLoadingRender = () => {
return <Skeleton active />
}
const paths = {
...import.meta.glob("/src/pages/**/[a-z[]*.jsx"),
...import.meta.glob("/src/pages/**/[a-z[]*.tsx"),
}
const pathsMobile = {
...import.meta.glob("/src/pages/**/[a-z[]*.mobile.jsx"),
...import.meta.glob("/src/pages/**/[a-z[]*.mobile.tsx"),
}
const routes = Object.keys(paths).map((route) => {
const path = route
.replace(/\/src\/pages|index|\.jsx$/g, "")
.replace(/\/src\/pages|index|\.tsx$/g, "")
.replace(/\[\.{3}.+\]/, "*")
.replace(/\[(.+)\]/, ":$1")
return {
path,
element: paths[route]
}
})
const mobileRoutes = Object.keys(pathsMobile).map((route) => {
const path = route
.replace(/\/src\/pages|index|\.mobile|\.jsx$/g, "")
.replace(/\/src\/pages|index|\.mobile|\.tsx$/g, "")
.replace(/\[\.{3}.+\]/, "*")
.replace(/\[(.+)\]/, ":$1")
return {
path,
element: pathsMobile[path]
}
})
function generatePageElementWrapper(route, element, bindProps) {
return React.createElement((props) => {
const params = useParams()
const url = new URL(window.location)
const query = new Proxy(url, {
get: (target, prop) => target.searchParams.get(prop),
})
const routeDeclaration = routesDeclaration.find((layout) => {
const routePath = layout.path.replace(/\*/g, ".*").replace(/!/g, "^")
return new RegExp(routePath).test(route)
}) ?? {
path: route,
useLayout: "default",
}
route = route.replace(/\?.+$/, "").replace(/\/{2,}/g, "/")
route = route.replace(/\/$/, "")
if (routeDeclaration) {
if (!bindProps.user && (window.location.pathname !== config.app?.authPath)) {
if (!routeDeclaration.public) {
if (typeof window.app.setLocation === "function") {
window.app.setLocation(config.app?.authPath ?? "/login")
return <div />
}
window.location.href = config.app?.authPath ?? "/login"
return <div />
}
}
if (typeof routeDeclaration.requiredRoles !== "undefined") {
const isAdmin = bindProps.user?.roles?.includes("admin") ?? false
if (!isAdmin && !routeDeclaration.requiredRoles.some((role) => bindProps.user?.roles?.includes(role))) {
return <Result
status="403"
title="403"
subTitle="Sorry, you are not authorized to access this page."
extra={<Button type="primary" onClick={() => window.app.setLocation("/")}>Back Home</Button>}
/>
}
}
if (routeDeclaration.useLayout) {
app.layout.set(routeDeclaration.useLayout)
}
// if (routeDeclaration.useHeader === true) {
// app.layout.header?.toggle(true)
// } else {
// app.layout.header?.toggle(false)
// }
if (typeof routeDeclaration.useTitle !== "undefined") {
if (typeof routeDeclaration.useTitle === "function") {
routeDeclaration.useTitle = routeDeclaration.useTitle(route, params)
}
document.title = `${routeDeclaration.useTitle} - ${config.app.siteName}`
} else {
document.title = config.app.siteName
}
if (routeDeclaration.centeredContent) {
app.layout.toogleCenteredContent(true)
} else {
app.layout.toogleCenteredContent(false)
}
}
return React.createElement(
loadable(element, {
fallback: React.createElement(bindProps.staticRenders?.PageLoad || DefaultLoadingRender),
}),
{
...props,
...bindProps,
url: url,
params: params,
query: query,
})
})
}
export const PageRender = React.memo((props) => {
const navigate = useNavigate()
app.location = useLocation()
React.useEffect(() => {
app.setLocation = async (to, state = {}) => {
// clean double slashes
to = to.replace(/\/{2,}/g, "/")
// if state is a number, it's a delay
if (typeof state !== "object") {
state = {}
}
const transitionDuration = app.cores.style.getValue("page-transition-duration") ?? "250ms"
state.transitionDelay = Number(transitionDuration.replace("ms", ""))
app.eventBus.emit("router.navigate", to, {
state,
})
app.location.lastPathname = app.location.pathname
if (state.transitionDelay >= 100) {
await new Promise((resolve) => {
setTimeout(() => {
resolve()
}, state.transitionDelay)
})
}
return navigate(to, {
state
})
}
app.backLocation = (state = {}) => {
let to = app.location.lastPathname
if (!to || to === app.location.pathname) {
console.warn("No last location found, redirecting to /")
to = "/"
}
return navigate(to, {
state
})
}
}, [])
return <Routes>
{
routes.map((route, index) => {
let Element = route.element
if (window.isMobile) {
const mobileElement = mobileRoutes.find((r) => r.path === route.path)
if (mobileElement) {
Element = mobileElement
}
}
return <Route
key={index}
path={route.path}
element={generatePageElementWrapper(route.path, route.element, props)}
exact
/>
})
}
<Route
path="*"
element={React.createElement(props.staticRenders?.NotFound || DefaultNotFoundRender)}
/>
</Routes>
})