sozsoft-platform/ui/src/routes/dynamicRouter.tsx
2026-09-08 17:00:15 +03:00

206 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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>
)
}