206 lines
6.8 KiB
TypeScript
206 lines
6.8 KiB
TypeScript
// DynamicRouter.tsx
|
||
import React from 'react'
|
||
import PageTitle from '@/components/shared/PageTitle'
|
||
import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
|
||
import { mapCustomComponentRoutes, mapDynamicRoutes } from './dynamicRouteLoader'
|
||
import { useDynamicRoutes } from './dynamicRoutesContext'
|
||
import { useComponents } from '@/contexts/ComponentContext'
|
||
import ProtectedRoute from '@/components/route/ProtectedRoute'
|
||
import PermissionGuard from '@/components/route/PermissionGuard'
|
||
import PageContainer from '@/components/template/PageContainer'
|
||
import { ROUTES_ENUM } from './route.constant'
|
||
import { hasSubdomain } from '@/utils/subdomain'
|
||
|
||
// AccessDenied ve NotFound'u dinamiklikten çıkarıyoruz
|
||
const AccessDenied = React.lazy(() => import('@/views/AccessDenied'))
|
||
const NotFound = React.lazy(() => import('@/views/NotFound'))
|
||
const DatabaseSetup = React.lazy(() => import('@/views/setup/DatabaseSetup'))
|
||
|
||
/**
|
||
* Physical views set their own title; this only fills the gap for routes whose
|
||
* component is compiled at runtime. `fallback` sayesinde kendi başlığını
|
||
* bildiren bir bileşen buradaki başlığı her zaman yener — eskiden bu, Helmet'in
|
||
* "sonra mount olan kazanır" davranışından geliyordu.
|
||
*/
|
||
const RouteTitle = ({ title }: { title?: string }) =>
|
||
title ? <PageTitle fallback title={title} /> : null
|
||
|
||
const RootRedirect = () => {
|
||
const location = useLocation()
|
||
const searchParams = new URLSearchParams(location.search)
|
||
const isPasswordResetLink = searchParams.has('userId') && searchParams.has('resetToken')
|
||
|
||
return (
|
||
<Navigate
|
||
to={
|
||
isPasswordResetLink
|
||
? `${ROUTES_ENUM.authenticated.resetPassword}${location.search}`
|
||
: hasSubdomain()
|
||
? ROUTES_ENUM.authenticated.login
|
||
: ROUTES_ENUM.public.home
|
||
}
|
||
replace
|
||
/>
|
||
)
|
||
}
|
||
|
||
const LegacyPasswordResetRedirect = () => {
|
||
const location = useLocation()
|
||
|
||
return <Navigate to={`${ROUTES_ENUM.authenticated.resetPassword}${location.search}`} replace />
|
||
}
|
||
|
||
const LegacyEmailConfirmationRedirect = () => {
|
||
const location = useLocation()
|
||
|
||
return <Navigate to={location.pathname.replace(/^\/account/, '')} replace />
|
||
}
|
||
|
||
export const DynamicRouter: React.FC = () => {
|
||
const { routes, loading, error } = useDynamicRoutes()
|
||
const { components, registeredComponents, renderComponent, isComponentRegistered } =
|
||
useComponents()
|
||
const location = useLocation()
|
||
|
||
const dynamicRoutes = React.useMemo(() => {
|
||
const customComponentRoutes = mapCustomComponentRoutes(components)
|
||
const claimedPaths = new Set(customComponentRoutes.map((route) => route.path))
|
||
|
||
// Ayni yolu iki route paylasirsa custom component kazanir: bir ekranin fiziksel
|
||
// sayfadan runtime bilesene devri (ve bileseni pasife alarak geri donusu) yalnizca
|
||
// Custom Components ekranindan yonetilir, rota kaydini silmeyi gerektirmez.
|
||
return [
|
||
...mapDynamicRoutes(routes).filter((route) => !claimedPaths.has(route.path)),
|
||
...customComponentRoutes,
|
||
]
|
||
}, [routes, components])
|
||
|
||
// /setup path'inde loading bekleme — setup route her zaman erişilebilir olmalı.
|
||
// bootstrapFailed durumu Layouts'ta, layout seçilmeden önce ele alınır.
|
||
const isSetupPath = location.pathname === ROUTES_ENUM.setup
|
||
|
||
if (loading && !isSetupPath) return <div>Loading...</div>
|
||
if (error && !isSetupPath) return <div>Hata: {error}</div>
|
||
|
||
return (
|
||
<Routes>
|
||
{/* ADMIN */}
|
||
<Route path="/admin/*" element={<ProtectedRoute />}>
|
||
{dynamicRoutes
|
||
.filter((r) => r.routeType === 'protected')
|
||
.map((route) => {
|
||
const Component = route.getComponent(
|
||
registeredComponents,
|
||
renderComponent,
|
||
isComponentRegistered,
|
||
)
|
||
return (
|
||
<Route
|
||
key={route.key}
|
||
path={route.path.replace(/^\/admin\/?/, '') || '.'}
|
||
element={
|
||
<PermissionGuard permissions={route.authority}>
|
||
<PageContainer>
|
||
<RouteTitle title={route.title} />
|
||
<React.Suspense fallback={<div>Loading {route.path}...</div>}>
|
||
<Component />
|
||
</React.Suspense>
|
||
</PageContainer>
|
||
</PermissionGuard>
|
||
}
|
||
/>
|
||
)
|
||
})}
|
||
|
||
{/* admin default */}
|
||
<Route index element={<Navigate to={ROUTES_ENUM.protected.dashboard} replace />} />
|
||
|
||
{/* admin access denied (statik) */}
|
||
<Route
|
||
path="access-denied"
|
||
element={
|
||
<PageContainer>
|
||
<React.Suspense fallback={<div>Loading...</div>}>
|
||
<AccessDenied />
|
||
</React.Suspense>
|
||
</PageContainer>
|
||
}
|
||
/>
|
||
|
||
{/* admin not found (statik) */}
|
||
<Route
|
||
path="*"
|
||
element={
|
||
<PageContainer>
|
||
<React.Suspense fallback={<div>Loading...</div>}>
|
||
<NotFound />
|
||
</React.Suspense>
|
||
</PageContainer>
|
||
}
|
||
/>
|
||
</Route>
|
||
|
||
{/* Auth/Public dinamik rotalar */}
|
||
{dynamicRoutes
|
||
.filter((r) =>
|
||
hasSubdomain() ? r.routeType === 'authenticated' : r.routeType !== 'protected',
|
||
)
|
||
.map((route) => {
|
||
const Component = route.getComponent(
|
||
registeredComponents,
|
||
renderComponent,
|
||
isComponentRegistered,
|
||
)
|
||
return (
|
||
<Route
|
||
key={route.key}
|
||
path={route.path}
|
||
element={
|
||
<>
|
||
<RouteTitle title={route.title} />
|
||
<React.Suspense fallback={<div>Loading {route.path}...</div>}>
|
||
<Component />
|
||
</React.Suspense>
|
||
</>
|
||
}
|
||
/>
|
||
)
|
||
})}
|
||
|
||
{/* root redirect */}
|
||
<Route path="/" element={<RootRedirect />} />
|
||
<Route path="/account/confirm/:userId/:token" element={<LegacyEmailConfirmationRedirect />} />
|
||
<Route path="/account/reset-password" element={<LegacyPasswordResetRedirect />} />
|
||
|
||
{/* public access denied (statik) */}
|
||
<Route
|
||
path="/access-denied"
|
||
element={
|
||
<React.Suspense fallback={<div>Loading...</div>}>
|
||
<AccessDenied />
|
||
</React.Suspense>
|
||
}
|
||
/>
|
||
|
||
{/* public not found (statik) */}
|
||
<Route
|
||
path="*"
|
||
element={
|
||
<React.Suspense fallback={<div>Loading...</div>}>
|
||
<NotFound />
|
||
</React.Suspense>
|
||
}
|
||
/>
|
||
|
||
{/* İlk kurulum — veritabanı mevcut değilse gösterilir */}
|
||
<Route
|
||
path={ROUTES_ENUM.setup}
|
||
element={
|
||
<React.Suspense fallback={<div>Loading...</div>}>
|
||
<DatabaseSetup />
|
||
</React.Suspense>
|
||
}
|
||
/>
|
||
</Routes>
|
||
)
|
||
}
|