Files
pfm/PostFundManagement.ConsoleUI/src/App.tsx
T
2026-10-22 15:42:36 +02:00

198 lines
8.3 KiB
TypeScript

import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from './lib/queryClient';
import { AuthProvider, ProtectedRoute, TenantAdminProtectedRoute } from './auth/AuthProvider';
import { ClaimProtectedRoute, TenantManagementProtectedRoute, useRbac } from './auth/rbac';
import { ToastProvider } from './components/ui/Toast';
import { AppLayout } from './components/layout/AppLayout';
const LandingPage = lazy(() => import('./pages/LandingPage').then(m => ({ default: m.LandingPage })));
const ExecutiveDashboard = lazy(() => import('./pages/ExecutiveDashboard').then(m => ({ default: m.ExecutiveDashboard })));
const GovernancePage = lazy(() => import('./pages/GovernancePage').then(m => ({ default: m.GovernancePage })));
const ProjectsPage = lazy(() => import('./pages/ProjectsPage').then(m => ({ default: m.ProjectsPage })));
const BeneficiariesPage = lazy(() => import('./pages/BeneficiariesPage').then(m => ({ default: m.BeneficiariesPage })));
const ChangeManagementPage = lazy(() => import('./pages/ChangeManagementPage').then(m => ({ default: m.ChangeManagementPage })));
const SiteVisitsPage = lazy(() => import('./pages/SiteVisitsPage').then(m => ({ default: m.SiteVisitsPage })));
const CompliancePage = lazy(() => import('./pages/CompliancePage').then(m => ({ default: m.CompliancePage })));
const GrantsPage = lazy(() => import('./pages/GrantsPage').then(m => ({ default: m.GrantsPage })));
const MonitoringPage = lazy(() => import('./pages/MonitoringPage').then(m => ({ default: m.MonitoringPage })));
const TenantManagementPage = lazy(() => import('./pages/TenantManagementPage').then(m => ({ default: m.TenantManagementPage })));
const SettingsPage = lazy(() => import('./pages/SettingsPage').then(m => ({ default: m.SettingsPage })));
const RouteLoading = () => (
<div className="flex items-center justify-center min-h-[400px] w-full text-slate-400">
<div className="flex flex-col items-center gap-3">
<div className="w-7 h-7 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
<span className="text-xs font-medium text-slate-400">Loading module...</span>
</div>
</div>
);
const DefaultRouteRedirect = () => {
const { canView } = useRbac();
if (canView('dashboard')) return <Navigate to="/dashboard" replace />;
if (canView('projects_milestones')) return <Navigate to="/projects" replace />;
if (canView('grant_financials')) return <Navigate to="/grants" replace />;
if (canView('evidence_risks')) return <Navigate to="/monitoring" replace />;
if (canView('governance')) return <Navigate to="/governance" replace />;
if (canView('beneficiaries')) return <Navigate to="/projects/beneficiaries" replace />;
if (canView('settings_users') || canView('settings_invites')) return <Navigate to="/settings" replace />;
return <Navigate to="/projects" replace />;
};
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<AuthProvider>
<ToastProvider>
<Suspense fallback={<RouteLoading />}>
<Routes>
{/* Public OIDC Login / Landing Page */}
<Route path="/" element={<LandingPage />} />
<Route path="/login" element={<LandingPage />} />
{/* Protected Backoffice Application Shell */}
<Route
element={
<ProtectedRoute>
<AppLayout />
</ProtectedRoute>
}
>
<Route
path="/dashboard"
element={
<ClaimProtectedRoute claim="dashboard">
<ExecutiveDashboard />
</ClaimProtectedRoute>
}
/>
<Route
path="/governance"
element={
<ClaimProtectedRoute claim="governance">
<GovernancePage />
</ClaimProtectedRoute>
}
/>
<Route
path="/projects"
element={
<ClaimProtectedRoute claim="projects_milestones">
<ProjectsPage />
</ClaimProtectedRoute>
}
/>
<Route
path="/projects/beneficiaries"
element={
<ClaimProtectedRoute claim="beneficiaries">
<BeneficiariesPage />
</ClaimProtectedRoute>
}
/>
<Route
path="/beneficiaries"
element={
<ClaimProtectedRoute claim="beneficiaries">
<BeneficiariesPage />
</ClaimProtectedRoute>
}
/>
<Route
path="/projects/change-management"
element={
<ClaimProtectedRoute claim="change_management">
<ChangeManagementPage />
</ClaimProtectedRoute>
}
/>
<Route
path="/change-management"
element={
<ClaimProtectedRoute claim="change_management">
<ChangeManagementPage />
</ClaimProtectedRoute>
}
/>
<Route
path="/projects/site-visits"
element={
<ClaimProtectedRoute claim="site_visits">
<SiteVisitsPage />
</ClaimProtectedRoute>
}
/>
<Route
path="/site-visits"
element={
<ClaimProtectedRoute claim="site_visits">
<SiteVisitsPage />
</ClaimProtectedRoute>
}
/>
<Route
path="/projects/compliance"
element={
<ClaimProtectedRoute claim="compliance">
<CompliancePage />
</ClaimProtectedRoute>
}
/>
<Route
path="/compliance"
element={
<ClaimProtectedRoute claim="compliance">
<CompliancePage />
</ClaimProtectedRoute>
}
/>
<Route
path="/grants"
element={
<ClaimProtectedRoute claim="grant_financials">
<GrantsPage />
</ClaimProtectedRoute>
}
/>
<Route
path="/monitoring"
element={
<ClaimProtectedRoute claim="evidence_risks">
<MonitoringPage />
</ClaimProtectedRoute>
}
/>
<Route
path="/tenants"
element={
<TenantManagementProtectedRoute>
<TenantManagementPage />
</TenantManagementProtectedRoute>
}
/>
<Route
path="/governance/tenants"
element={
<TenantManagementProtectedRoute>
<TenantManagementPage />
</TenantManagementProtectedRoute>
}
/>
<Route path="/settings" element={<SettingsPage />} />
</Route>
{/* Fallback Redirect */}
<Route path="*" element={<DefaultRouteRedirect />} />
</Routes>
</Suspense>
</ToastProvider>
</AuthProvider>
</BrowserRouter>
</QueryClientProvider>
);
}