user management

This commit is contained in:
yaschalew
2026-06-13 14:32:26 +03:00
parent 12b6406d23
commit eee7b719b0
592 changed files with 267087 additions and 31 deletions

View File

@@ -12,6 +12,7 @@ import {
Truck,
Container,
Package,
Users,
//TrainTrack,
} from "lucide-react";
@@ -47,6 +48,7 @@ import FleetResourcePage from "./pages/fleet/FleetResourcePage";
import { getCategorySidebarChildren } from "./pages/ruleEngine/config/resources";
import TrainDetailPage from "./pages/trains/TrainDetailPage";
import RoutesPage from "./pages/fleet/RoutesPage";
import UserManagementHostPage from './features/user-management-host/UserManagementHostPage';
const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [
{
@@ -58,6 +60,11 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [
href: "/dashboard/overview",
icon: <LayoutDashboard />,
},
{
label: "UM",
href: "/um",
icon: <Users />,
},
{
label: "Booking requests",
href: "/dashboard/booking-requests",
@@ -244,7 +251,8 @@ const App = () => {
return (
<Routes>
<Route path="/auth" element={<LoginPage />} />
<Route path="*" element={<Navigate to="/auth" replace />} />
<Route path='/um/*' element={<UserManagementHostPage />} />
<Route path="*" element={<Navigate to="/um" replace />} />
</Routes>
);
}
@@ -252,8 +260,8 @@ const App = () => {
return (
<Routes>
<Route path="/" element={<Navigate to="/dashboard/overview" replace />} />
<Route path="/dashboard" element={<Navigate to="/dashboard/overview" replace />} />
<Route path="/dashboard" element={<Navigate to="/dashboard/overview" replace />} />,
<Route path='/um/*' element={<UserManagementHostPage />} />
<Route path="/dashboard" element={<DashboardShell />}>
<Route path="overview" element={<OverviewPage />} />

View File

@@ -4,7 +4,6 @@ import { AuthContext } from "./AuthProvider";
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within AuthProvider");
}

View File

@@ -0,0 +1,92 @@
import { useEffect, useRef, useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
/**
* Same-origin host for the user-management module.
*
* The host app (React 19 / Mantine 8 / Tailwind 3) embeds the module (React 18 /
* Mantine 7 / Tailwind 4) via an iframe so the two never share a React tree,
* router, or CSS — the version mismatch is fully isolated by the document
* boundary. The module is built into apps/backoffice/public/_um and served by
* THIS same server at <origin>/_um/, so there is no second server and no second
* port. Override the mount path with VITE_USER_MANAGEMENT_BASE (default /_um).
*
* SSO: the module and host authenticate against the SAME backend, so the host's
* token is valid in the module. The module posts `UM_REQUEST_AUTH`; we reply with
* our stored token. Route-sync mirrors the module's internal route into the host
* URL (/um/<path>) so a refresh deep-links back to the selected menu.
*/
function readToken(): string | null {
return (
localStorage.getItem('fhc-backoffice-auth-token') ??
(() => {
const escaped = 'auth-token'.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1');
const match = document.cookie.match(new RegExp('(?:^|; )' + escaped + '=([^;]*)'));
return match ? decodeURIComponent(match[1]) : null;
})()
);
}
function readRefreshToken(): string | null {
return localStorage.getItem('fhc-backoffice-auth-refresh-token') ?? null;
}
export default function UserManagementHostPage() {
const navigate = useNavigate();
const location = useLocation();
const iframeRef = useRef<HTMLIFrameElement>(null);
// Same-origin sub-path the module is served from (matches the module's Vite
// `base` + the apps/backoffice/public/_um build). Same origin ⇒ no second port.
const mountBase = (
(import.meta.env.VITE_USER_MANAGEMENT_BASE as string | undefined) ?? '/_um'
).replace(/\/$/, '');
const moduleOrigin = window.location.origin;
// Deep-link: the host route is /um/*, so whatever follows /um is the module's
// own route. Compute src ONCE (frozen) so later parent-URL updates don't reload.
const [iframeSrc] = useState(() => {
const sub = location.pathname.replace(/^\/um(?=\/|$)/, '');
return mountBase + sub + location.search;
});
useEffect(() => {
const onMessage = (event: MessageEvent) => {
if (event.origin !== moduleOrigin) return;
const data = event.data as { type?: string; path?: string } | undefined;
if (!data) return;
if (data.type === 'UM_REQUEST_AUTH') {
const token = readToken();
const refreshToken = readRefreshToken();
const target = iframeRef.current?.contentWindow;
if (token && target) {
target.postMessage({ type: 'UM_AUTH_TOKEN', token, refreshToken }, moduleOrigin);
}
return;
}
if (data.type === 'UM_ROUTE_CHANGED' && typeof data.path === 'string') {
const target = '/um' + data.path;
if (window.location.pathname + window.location.search !== target) {
navigate(target, { replace: true });
}
}
};
window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
}, [moduleOrigin, navigate]);
return (
<div style={{ position: 'fixed', inset: 0 }}>
<iframe
ref={iframeRef}
title="User Management"
src={iframeSrc}
style={{ width: '100%', height: '100%', border: 0, display: 'block' }}
/>
</div>
);
}

View File

@@ -176,12 +176,13 @@ const LoginPage = () => {
setNormalizedIdentifier(normalized);
const result = await login({ email: normalized, password });
console.log(result)
if (result.mfaRequired) {
setNeedsMfa(true);
return;
}
navigate("/dashboard/overview", { replace: true });
// navigate("/dashboard/overview", { replace: true });
} catch {
setError("Unable to sign in with those credentials.");
} finally {