switch user management to package implmentation

This commit is contained in:
Dagimassefa
2026-06-21 12:11:35 +03:00
parent 45383a4f43
commit 8111606e89
7 changed files with 841 additions and 476 deletions

View File

@@ -213,41 +213,13 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [
},
],
},
{
title: "Administration",
items: [
{
label: "User management",
href: "/dashboard/user-management",
icon: <Network />,
permission: FREIGHT_PERMS.admin,
children: [
{
label: "Users",
href: "/dashboard/user-management/users",
},
{
label: "Employees",
href: "/dashboard/user-management/employees",
},
{
label: "Position Types",
href: "/dashboard/user-management/position-types",
},
{
label: "Permissions",
href: "/dashboard/user-management/permissions",
},
{
label: "Roles",
href: "/dashboard/user-management/roles",
},
],
},
{
label: "File settings",
href: "/dashboard/file-settings",
icon: <Paperclip />,
{
title: "Administration",
items: [
{
label: "File settings",
href: "/dashboard/file-settings",
icon: <Paperclip />,
permission: FREIGHT_PERMS.admin,
},
{
@@ -340,224 +312,216 @@ const App = () => {
return <LoadingScreen />;
}
if (!user) {
return (
<Routes>
<Route path="/auth" element={<LoginPage />} />
<Route path='/um/*' element={<UserManagementHostPage />} />
<Route path="*" element={<Navigate to="/um" replace />} />
</Routes>
);
}
if (!user) {
return (
<Routes>
<Route path="/auth" element={<LoginPage />} />
<Route path="/um/*" element={<UserManagementHostPage />} />
<Route path="*" element={<Navigate to="/auth" replace />} />
</Routes>
);
}
return (
<Routes>
<Route path="/um/*" element={<UserManagementHostPage />} />
<Route path="/" element={<Navigate to="/dashboard/overview" replace />} />
<Route path="/dashboard" element={<Navigate to="/dashboard/overview" replace />} />
<Route path="/dashboard" element={<DashboardShell />}>
<Route path="overview" element={<OverviewPage />} />
<Route path="profile" element={<MyProfilePage />} />
<Route path="booking-requests" element={<BookingRequestsPage />} />
<Route
path="payments"
element={
<RequirePermission permission={FREIGHT_PERMS.bookings.view}>
<PaymentsPage />
</RequirePermission>
}
/>
<Route path="booking-requests/new" element={<NewBookingPage />} />
<Route path="booking-requests/:id" element={<BookingRequestDetailPage />} />
<Route
path="booking-requests/:id/contract"
element={<BookingContractPage />}
/>
<Route path="warehouses" element={<WarehouseListPage />} />
<Route path="warehouses/:id" element={<WarehouseDetailPage />} />
<Route path="warehouse-inventory" element={<WarehouseInventoryPage />} />
<Route path="arrival-queue" element={<ArrivalQueuePage />} />
<Route path="loading-queue" element={<LoadingQueuePage />} />
<Route path="loaded-inventory" element={<LoadedInventoryPage />} />
<Route path="dispatch-queue" element={<DispatchQueuePage />} />
<Route path="inventory-inquiry" element={<InventoryInquiryPage />} />
<Route path="warehouse-rules" element={<WarehouseRulesPage />} />
<Route path="warehouse-fee-invoices" element={<WarehouseInvoicesPage />} />
<Route path="warehouse-dashboard" element={<WarehouseDashboardPage />} />
return (
<Routes>
<Route path="/" element={<Navigate to="/dashboard/overview" replace />} />
<Route path="/dashboard" element={<Navigate to="/dashboard/overview" replace />} />
<Route path="/dashboard" element={<DashboardShell />}>
<Route path="overview" element={<OverviewPage />} />
<Route path="profile" element={<MyProfilePage />} />
<Route path="booking-requests" element={<BookingRequestsPage />} />
<Route
path="payments"
element={
<RequirePermission permission={FREIGHT_PERMS.bookings.view}>
<PaymentsPage />
</RequirePermission>
}
/>
<Route path="booking-requests/new" element={<NewBookingPage />} />
<Route path="booking-requests/:id" element={<BookingRequestDetailPage />} />
<Route
path="booking-requests/:id/contract"
element={<BookingContractPage />}
/>
<Route path="warehouses" element={<WarehouseListPage />} />
<Route path="warehouses/:id" element={<WarehouseDetailPage />} />
<Route path="warehouse-inventory" element={<WarehouseInventoryPage />} />
<Route path="arrival-queue" element={<ArrivalQueuePage />} />
<Route path="loading-queue" element={<LoadingQueuePage />} />
<Route path="loaded-inventory" element={<LoadedInventoryPage />} />
<Route path="dispatch-queue" element={<DispatchQueuePage />} />
<Route path="inventory-inquiry" element={<InventoryInquiryPage />} />
<Route path="warehouse-rules" element={<WarehouseRulesPage />} />
<Route path="warehouse-fee-invoices" element={<WarehouseInvoicesPage />} />
<Route path="warehouse-dashboard" element={<WarehouseDashboardPage />} />
<Route
path="operations/train-scheduling"
element={<Navigate to="/dashboard/operations/train-scheduling-v2" replace />}
/>
<Route
path="operations/batch-board"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<BatchBoardPage />
</RequirePermission>
}
/>
<Route
path="operations/batch-board/:scheduleId"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<BatchScheduleDetailPage />
</RequirePermission>
}
/>
<Route
path="operations/train-scheduling-v2"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<TrainScheduleV2ListPage />
</RequirePermission>
}
/>
<Route
path="operations/train-scheduling-v2/:scheduleId"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<TrainScheduleV2DetailPage />
</RequirePermission>
}
/>
<Route
path="operations/train-scheduling-v2/:scheduleId/track"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<TrainScheduleTrackPage />
</RequirePermission>
}
/>
<Route
path="routes"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<RoutesPage />
</RequirePermission>
}
/>
<Route
path="locomotives"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<FleetResourcePage />
</RequirePermission>
}
/>
<Route
path="trains"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<FleetResourcePage />
</RequirePermission>
}
/>
<Route
path="trains/:id"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<TrainDetailPage />
</RequirePermission>
}
/>
<Route
path="wagons"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<FleetResourcePage />
</RequirePermission>
}
/>
<Route
path="containers"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<FleetResourcePage />
</RequirePermission>
}
/>
<Route
path="cargoes"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<FleetResourcePage />
</RequirePermission>
}
/>
{/* iframe-based user management module */}
<Route path="um/*" element={<UserManagementHostPage />} />
{/* Legacy embedded user management routes */}
<Route path="user-management" element={<UserManagementPage />} />
<Route path="user-management/users" element={<UsersPage />} />
<Route path="user-management/position-types" element={<PositionTypesPage />} />
{/* <Route path="user-management/employees" element={<EmployeesPage />} /> */}
<Route path="user-management/permissions" element={<PermissionsPage />} />
<Route path="user-management/roles" element={<RolesPage />} />
<Route
path="file-settings"
element={
<RequirePermission permission={FREIGHT_PERMS.admin}>
<FileUploadSettingsPage />
</RequirePermission>
}
/>
<Route
path="dropdown-settings"
element={
<RequirePermission permission={FREIGHT_PERMS.admin}>
<DropdownSettingsPage />
</RequirePermission>
}
/>
<Route
path="configuration"
element={<Navigate to="/dashboard/configuration/cargo-types" replace />}
/>
<Route
path="configuration/train-scheduling-rules"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<TrainSchedulingGlobalRulesPage />
</RequirePermission>
}
/>
<Route path="configuration/cargo-types" element={<CargoTypesPage />} />
<Route path="configuration/cargo-types/:id" element={<CargoTypesPage />} />
<Route path="configuration/:resource" element={<RuleEngineResourcePage />} />
<Route
path="rules"
element={<Navigate to="/dashboard/rules/priority-configs" replace />}
/>
<Route path="rules/:resource" element={<RuleEngineResourcePage />} />
<Route
path="rule-engine"
element={<Navigate to="/dashboard/configuration/cargo-types" replace />}
/>
<Route path="rule-engine/:resource" element={<RuleEngineLegacyRedirect />} />
<Route path="user1" element={<DemoUser1Page />} />
<Route path="user2" element={<DemoUser2Page />} />
<Route
path="org-structure"
element={<Navigate to="/dashboard/user-management" replace />}
/>
<Route
path="org-structure/*"
element={<Navigate to="/dashboard/user-management" replace />}
/>
</Route>
<Route path="*" element={<Navigate to="/dashboard/overview" replace />} />
</Routes>
);
};
<Route
path="operations/train-scheduling"
element={<Navigate to="/dashboard/operations/train-scheduling-v2" replace />}
/>
<Route
path="operations/batch-board"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<BatchBoardPage />
</RequirePermission>
}
/>
<Route
path="operations/batch-board/:scheduleId"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<BatchScheduleDetailPage />
</RequirePermission>
}
/>
<Route
path="operations/train-scheduling-v2"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<TrainScheduleV2ListPage />
</RequirePermission>
}
/>
<Route
path="operations/train-scheduling-v2/:scheduleId"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<TrainScheduleV2DetailPage />
</RequirePermission>
}
/>
<Route
path="operations/train-scheduling-v2/:scheduleId/track"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<TrainScheduleTrackPage />
</RequirePermission>
}
/>
<Route
path="routes"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<RoutesPage />
</RequirePermission>
}
/>
<Route
path="locomotives"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<FleetResourcePage />
</RequirePermission>
}
/>
<Route
path="trains"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<FleetResourcePage />
</RequirePermission>
}
/>
<Route
path="trains/:id"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<TrainDetailPage />
</RequirePermission>
}
/>
<Route
path="wagons"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<FleetResourcePage />
</RequirePermission>
}
/>
<Route
path="containers"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<FleetResourcePage />
</RequirePermission>
}
/>
<Route
path="cargoes"
element={
<RequirePermission permission={FREIGHT_PERMS.fleet.view}>
<FleetResourcePage />
</RequirePermission>
}
/>
{/* Legacy embedded user management routes */}
<Route path="user-management" element={<UserManagementPage />} />
<Route path="user-management/users" element={<UsersPage />} />
<Route path="user-management/position-types" element={<PositionTypesPage />} />
{/* <Route path="user-management/employees" element={<EmployeesPage />} /> */}
<Route path="user-management/permissions" element={<PermissionsPage />} />
<Route path="user-management/roles" element={<RolesPage />} />
<Route
path="file-settings"
element={
<RequirePermission permission={FREIGHT_PERMS.admin}>
<FileUploadSettingsPage />
</RequirePermission>
}
/>
<Route
path="dropdown-settings"
element={
<RequirePermission permission={FREIGHT_PERMS.admin}>
<DropdownSettingsPage />
</RequirePermission>
}
/>
<Route
path="configuration"
element={<Navigate to="/dashboard/configuration/cargo-types" replace />}
/>
<Route
path="configuration/train-scheduling-rules"
element={
<RequirePermission permission={FREIGHT_PERMS.trainScheduling.view}>
<TrainSchedulingGlobalRulesPage />
</RequirePermission>
}
/>
<Route path="configuration/cargo-types" element={<CargoTypesPage />} />
<Route path="configuration/cargo-types/:id" element={<CargoTypesPage />} />
<Route path="configuration/:resource" element={<RuleEngineResourcePage />} />
<Route
path="rules"
element={<Navigate to="/dashboard/rules/priority-configs" replace />}
/>
<Route path="rules/:resource" element={<RuleEngineResourcePage />} />
<Route
path="rule-engine"
element={<Navigate to="/dashboard/configuration/cargo-types" replace />}
/>
<Route path="rule-engine/:resource" element={<RuleEngineLegacyRedirect />} />
<Route path="user1" element={<DemoUser1Page />} />
<Route path="user2" element={<DemoUser2Page />} />
<Route path="org-structure" element={<Navigate to="/um" replace />} />
<Route path="org-structure/*" element={<Navigate to="/um" replace />} />
</Route>
<Route path="*" element={<Navigate to="/dashboard/overview" replace />} />
</Routes>
);
};
export default App;

View File

@@ -1,113 +1,82 @@
import { useEffect, useRef, useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { getCookie } from '@/auth/cookies';
import { useEffect, useRef } from "react";
import { createRoot, type Root } from "react-dom/client";
import {
UserManagementApp,
type UserManagementRuntimeOptions,
type UserManagementSessionSeed,
} from "@tria-plc/iamui";
function readToken(): string | null {
return getCookie('auth-token') ?? null;
}
import { getCookie } from "@/auth/cookies";
function readRefreshToken(): string | null {
return getCookie('refresh-token') ?? null;
import { iamConfig } from "./iamConfig";
function readInitialSession(): UserManagementSessionSeed | null {
const token = getCookie("auth-token");
if (!token) {
return null;
}
const refreshToken = getCookie("refresh-token") ?? undefined;
return {
token,
refreshToken,
rememberMe: true,
};
}
export default function UserManagementHostPage() {
const navigate = useNavigate();
const location = useLocation();
const iframeRef = useRef<HTMLIFrameElement>(null);
const mountRef = useRef<HTMLDivElement | null>(null);
const rootRef = useRef<Root | null>(null);
const unmountTimerRef = useRef<number | null>(null);
const mountBase = (
(import.meta.env.VITE_USER_MANAGEMENT_BASE as string | undefined) ?? '/_um'
).replace(/\/$/, '');
const moduleOrigin = window.location.origin;
const [iframeSrc] = useState(() => {
const sub = location.pathname.replace(/^\/(?:dashboard\/)?um(?=\/|$)/, '');
return mountBase + (sub || '/') + location.search;
});
// ✅ Send token when iframe loads
const handleIframeLoad = () => {
const token = readToken();
const refreshToken = readRefreshToken();
const target = iframeRef.current?.contentWindow;
if (!token) {
console.warn('⚠️ No authentication token found');
return;
}
if (!target) {
console.warn('⚠️ No iframe reference');
return;
}
target.postMessage(
{
type: 'UM_AUTH_TOKEN',
token,
refreshToken,
},
moduleOrigin
);
console.log('✅ Token sent to iframe module');
};
// ✅ Listen for messages from iframe
useEffect(() => {
const onMessage = (event: MessageEvent) => {
// Security: Only accept from same origin
if (event.origin !== moduleOrigin) {
console.warn('🚫 Blocked message from different origin:', event.origin);
return;
}
const mountNode = mountRef.current;
const data = event.data as { type?: string; path?: string } | undefined;
if (!data) return;
if (!mountNode) {
return;
}
// Handle auth request (if module asks for token again)
if (data.type === 'UM_REQUEST_AUTH') {
const token = readToken();
const refreshToken = readRefreshToken();
const target = iframeRef.current?.contentWindow;
if (unmountTimerRef.current !== null) {
window.clearTimeout(unmountTimerRef.current);
unmountTimerRef.current = null;
}
if (token && target) {
target.postMessage(
{
type: 'UM_AUTH_TOKEN',
token,
refreshToken,
},
moduleOrigin
);
console.log('✅ Token resent to iframe (on request)');
}
return;
}
if (!rootRef.current) {
rootRef.current = createRoot(mountNode);
}
// Handle route synchronization
if (data.type === 'UM_ROUTE_CHANGED' && typeof data.path === 'string') {
const target = '/dashboard/um' + data.path;
if (window.location.pathname + window.location.search !== target) {
navigate(target, { replace: true });
}
}
const apiBaseUrl = import.meta.env.VITE_BASE_API_URL.replace(/\/+$/, "");
const iamApiUrl = "/um-api";
const runtime: UserManagementRuntimeOptions = {
basename: "/um",
apiBaseUrl,
apiUrl: iamApiUrl,
recordApiUrl: iamApiUrl,
chronicleUrl: iamApiUrl,
auditApiUrl: iamApiUrl,
};
window.addEventListener('message', onMessage);
return () => window.removeEventListener('message', onMessage);
}, [moduleOrigin, navigate]);
rootRef.current.render(
<UserManagementApp
config={iamConfig}
runtime={runtime}
session={{
initialSession: readInitialSession(),
enableEmbeddedAuthBridge: false,
}}
/>,
);
return (
<div style={{ position: 'fixed', inset: 0 }}>
<iframe
ref={iframeRef}
title="User Management"
src={iframeSrc}
onLoad={handleIframeLoad}
style={{ width: '100%', height: '100%', border: 0, display: 'block' }}
/>
</div>
);
return () => {
unmountTimerRef.current = window.setTimeout(() => {
rootRef.current?.unmount();
rootRef.current = null;
unmountTimerRef.current = null;
}, 0);
};
}, []);
return <div ref={mountRef} style={{ position: "fixed", inset: 0 }} />;
}

View File

@@ -0,0 +1,36 @@
import type { DesignConfig } from "@tria-plc/iamui";
export const iamConfig: DesignConfig = {
brand: {
appName: "EDR Freight Backoffice",
logoUrl: "/assets/logo.svg",
},
colors: {
primary: "#1d7754",
primaryForeground: "#ffffff",
secondary: "#ebb53d",
background: "#f6f8f4",
foreground: "#12261c",
border: "#d7e1da",
muted: "#eaf1ec",
mutedForeground: "#557164",
card: "#ffffff",
danger: "#c93d2b",
},
typography: {
fontFamily: "Inter, ui-sans-serif, system-ui, sans-serif",
headingFontFamily: "Inter, ui-sans-serif, system-ui, sans-serif",
},
shape: {
radius: "0.875rem",
},
layout: {
userManagementView: "classic",
showTopBar: true,
sidebarBrandLabel: "User Management",
sidebarBrandSublabel: "EDR Freight",
},
appearance: {
colorScheme: "light",
},
};