250 lines
9.9 KiB
TypeScript
250 lines
9.9 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { BrowserRouter, Routes, Route, useLocation, Navigate } from 'react-router-dom';
|
|
import { Sidebar } from './components/Sidebar';
|
|
import { TopBar } from './components/TopBar';
|
|
import { ErrorBoundary } from './components/ErrorBoundary';
|
|
import { Dashboard } from './pages/Dashboard';
|
|
import { LiveIncidents } from './pages/LiveIncidents';
|
|
import { FleetDispatch } from './pages/FleetDispatch';
|
|
import { PatientClinical } from './pages/PatientClinical';
|
|
import { HospitalsNetwork } from './pages/HospitalsNetwork';
|
|
import { AnalyticsReports } from './pages/AnalyticsReports';
|
|
import { UserManagement } from './pages/UserManagement';
|
|
import { PlatformConfig } from './pages/PlatformConfig';
|
|
import { AuditCompliance } from './pages/AuditCompliance';
|
|
import { SystemHealth } from './pages/SystemHealth';
|
|
import { HospitalConsole } from './pages/HospitalConsole';
|
|
import { MasterDataManagement } from './pages/MasterData';
|
|
import { CallerPortal } from './pages/CallerPortal';
|
|
import { Login } from './pages/Login';
|
|
import { FleetLogin } from './pages/FleetLogin';
|
|
import { FleetOperatorDashboard } from './pages/FleetOperatorDashboard';
|
|
import { PerspectiveLauncher } from './pages/PerspectiveLauncher';
|
|
import { RoleLogin } from './pages/RoleLogin';
|
|
import { HospitalLogin } from './pages/HospitalLogin';
|
|
import { ComingSoonPortal } from './pages/ComingSoonPortal';
|
|
import {
|
|
Building2,
|
|
Stethoscope,
|
|
Activity,
|
|
User,
|
|
Scan,
|
|
ShoppingCart
|
|
} from 'lucide-react';
|
|
|
|
import { isTokenExpired, logout } from './utils/auth';
|
|
|
|
// --- ROLE-BASED ACCESS CONTROL ---
|
|
const RoleProtectedRoute: React.FC<{
|
|
children: React.ReactNode,
|
|
allowedRoles: string[],
|
|
user: any
|
|
}> = ({ children, allowedRoles, user }) => {
|
|
const isAuthenticated = localStorage.getItem('teleems_auth') === 'true';
|
|
|
|
if (!isAuthenticated) return <Navigate to="/login" replace />;
|
|
|
|
const userRoles = Array.isArray(user?.roles)
|
|
? user.roles.map((r: any) => String(r).toUpperCase().replace(/\s+/g, '_'))
|
|
: [];
|
|
const hasAccess = allowedRoles.some(role => userRoles.includes(role)) || userRoles.includes('CURESELECT_ADMIN');
|
|
|
|
if (!hasAccess) {
|
|
console.log('[RBAC] Access Denied:', { required: allowedRoles, current: userRoles });
|
|
// Redirect to their respective "home" if they don't have access
|
|
if (userRoles.includes('FLEET_OPERATOR')) return <Navigate to="/fleet-operator" replace />;
|
|
return <Navigate to="/" replace />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
};
|
|
|
|
function AppContent() {
|
|
const location = useLocation();
|
|
|
|
// --- SESSION MONITORING ---
|
|
// Periodically check if the token has expired
|
|
useEffect(() => {
|
|
const checkSession = () => {
|
|
const token = localStorage.getItem('teleems_token');
|
|
const auth = localStorage.getItem('teleems_auth') === 'true';
|
|
|
|
if (auth && token && isTokenExpired(token)) {
|
|
console.warn('Session expired. Logging out...');
|
|
logout();
|
|
}
|
|
};
|
|
|
|
// Check on mount
|
|
checkSession();
|
|
|
|
// Check on every route change
|
|
checkSession();
|
|
|
|
// Periodically check every 30 seconds
|
|
const interval = setInterval(checkSession, 30000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [location.pathname]);
|
|
|
|
// --- DEVELOPMENT BYPASS ---
|
|
// In a real production app, this would be removed.
|
|
// For the user's request: "this admin so don't want login give me admin level access"
|
|
/* Commented out to allow testing of launcher and login flow
|
|
useEffect(() => {
|
|
const isAuth = localStorage.getItem('teleems_auth') === 'true';
|
|
const isCaller = window.location.pathname === '/caller';
|
|
const isLogin = window.location.pathname === '/login';
|
|
|
|
if (!isAuth && !isCaller && !isLogin) {
|
|
console.log('Dev Mode: Auto-authenticating as CureSelect Super Admin');
|
|
localStorage.setItem('teleems_auth', 'true');
|
|
localStorage.setItem('teleems_token', 'dev-super-token-2026');
|
|
localStorage.setItem('teleems_user', JSON.stringify({
|
|
id: 'admin-001',
|
|
username: 'CureSelect Super Admin',
|
|
roles: ['CURESELECT_ADMIN', 'ADMIN'],
|
|
metadata: {
|
|
organization: { company_name: 'CureSelect Healthcare LLP' }
|
|
}
|
|
}));
|
|
// Force reload to update navigation
|
|
window.location.reload();
|
|
}
|
|
}, []);
|
|
*/
|
|
|
|
const isLoginPage = location.pathname.startsWith('/login') || location.pathname === '/fleet-login' || location.pathname === '/launcher';
|
|
|
|
const isAuthenticated = localStorage.getItem('teleems_auth') === 'true';
|
|
const user = JSON.parse(localStorage.getItem('teleems_user') || '{}');
|
|
|
|
// --- PUBLIC ROUTES (No Auth Required) ---
|
|
if (isLoginPage || (location.pathname === '/' && !isAuthenticated)) {
|
|
return (
|
|
<Routes>
|
|
<Route path="/" element={<PerspectiveLauncher />} />
|
|
<Route path="/login" element={<Login />} />
|
|
<Route path="/login/hospital" element={<HospitalLogin />} />
|
|
<Route path="/login/:role" element={<RoleLogin />} />
|
|
<Route path="/fleet-login" element={<FleetLogin />} />
|
|
<Route path="/launcher" element={<PerspectiveLauncher />} />
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
);
|
|
}
|
|
|
|
// --- PROTECTED ROUTES (Auth Required) ---
|
|
if (!isAuthenticated) {
|
|
return <Navigate to="/login" replace />;
|
|
}
|
|
|
|
return (
|
|
<div className="dashboard-container">
|
|
<ErrorBoundary>
|
|
<Sidebar />
|
|
</ErrorBoundary>
|
|
<main className="main-content">
|
|
<div className="scanline" />
|
|
<TopBar />
|
|
<div style={{ flex: 1, overflow: 'hidden', position: 'relative', display: 'flex', flexDirection: 'column' }}>
|
|
<ErrorBoundary>
|
|
<Routes>
|
|
<Route path="/" element={
|
|
isAuthenticated ? (
|
|
user?.roles?.includes('FLEET_OPERATOR') && !user?.roles?.includes('CURESELECT_ADMIN')
|
|
? <Navigate to="/fleet-operator" replace />
|
|
: <Dashboard />
|
|
) : (
|
|
<Navigate to="/launcher" replace />
|
|
)
|
|
} />
|
|
<Route path="/incidents" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN', 'PILOT', 'COORDINATOR']} user={user}>
|
|
<LiveIncidents />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/fleet" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN', 'STATION_INCHARGE']} user={user}>
|
|
<FleetDispatch />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/clinical" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN', 'HOSPITAL_ADMIN', 'ED_DOCTOR', 'EMT']} user={user}>
|
|
<PatientClinical />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/hospitals" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN']} user={user}>
|
|
<HospitalsNetwork />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/analytics" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN', 'HOSPITAL_ADMIN', 'STATION_INCHARGE']} user={user}>
|
|
<AnalyticsReports />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/users" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN']} user={user}>
|
|
<UserManagement />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/config" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN']} user={user}>
|
|
<PlatformConfig />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/compliance" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN', 'HOSPITAL_ADMIN']} user={user}>
|
|
<AuditCompliance />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/health" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN']} user={user}>
|
|
<SystemHealth />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/hospital-console" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN', 'HOSPITAL_ADMIN', 'ED_DOCTOR', 'COORDINATOR', 'EMT']} user={user}>
|
|
<HospitalConsole />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/master-data" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN']} user={user}>
|
|
<MasterDataManagement />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
<Route path="/caller" element={<CallerPortal />} />
|
|
<Route path="/fleet-operator" element={
|
|
<RoleProtectedRoute allowedRoles={['CURESELECT_ADMIN', 'FLEET_OPERATOR']} user={user}>
|
|
<FleetOperatorDashboard />
|
|
</RoleProtectedRoute>
|
|
} />
|
|
|
|
{/* --- NEW PERSPECTIVE PORTALS --- */}
|
|
<Route path="/launcher" element={<PerspectiveLauncher />} />
|
|
<Route path="/hospital-group" element={<ComingSoonPortal title="Hospital Group" icon={Building2} />} />
|
|
<Route path="/provider" element={<ComingSoonPortal title="Provider" icon={Stethoscope} />} />
|
|
<Route path="/provider-react" element={<ComingSoonPortal title="Provider React" icon={Activity} />} />
|
|
<Route path="/patient-portal" element={<ComingSoonPortal title="Patient" icon={User} />} />
|
|
<Route path="/scan-centre" element={<ComingSoonPortal title="Scan Centre" icon={Scan} />} />
|
|
<Route path="/cart" element={<ComingSoonPortal title="Cart / Mobile" icon={ShoppingCart} />} />
|
|
|
|
</Routes>
|
|
</ErrorBoundary>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function App() {
|
|
return (
|
|
<BrowserRouter>
|
|
<AppContent />
|
|
</BrowserRouter>
|
|
);
|
|
}
|
|
|
|
export default App;
|