diff --git a/apps/edr-freight-web/backoffice/package.json b/apps/edr-freight-web/backoffice/package.json
index 96dd20172..e8c9d21e6 100644
--- a/apps/edr-freight-web/backoffice/package.json
+++ b/apps/edr-freight-web/backoffice/package.json
@@ -32,6 +32,7 @@
"react-router-dom": "^6.27.0",
"recharts": "^3.8.1",
"sonner": "^2.0.7",
+ "stream-browserify": "^3.0.0",
"tailwind-merge": "^3.6.0",
"tinymce": "^8.6.0",
"zustand": "^5.0.0"
diff --git a/apps/edr-freight-web/backoffice/src/App.tsx b/apps/edr-freight-web/backoffice/src/App.tsx
index 11e4aa8eb..aae725618 100644
--- a/apps/edr-freight-web/backoffice/src/App.tsx
+++ b/apps/edr-freight-web/backoffice/src/App.tsx
@@ -49,14 +49,13 @@ import { getCategorySidebarChildren } from "./pages/ruleEngine/config/resources"
import RuleEngineLegacyRedirect from "./pages/ruleEngine/RuleEngineLegacyRedirect";
import RuleEngineResourcePage from "./pages/ruleEngine/RuleEngineResourcePage";
import TrainDetailPage from "./pages/trains/TrainDetailPage";
-import CargoTypesPage from "./pages/ruleEngine/CargoTypesPage";
-import TrainScheduleV2ListPage from "./pages/trainScheduling/TrainScheduleV2ListPage";
-import BatchBoardPage from "./pages/trainScheduling/BatchBoardPage";
-import BatchScheduleDetailPage from "./pages/trainScheduling/BatchScheduleDetailPage";
-import TrainScheduleTrackPage from "./pages/trainScheduling/TrainScheduleTrackPage";
-import TrainScheduleV2DetailPage from "./pages/trainScheduling/TrainScheduleV2DetailPage";
-import TrainScheduleV2ListPage from "./pages/trainScheduling/TrainScheduleV2ListPage";
-import TrainSchedulingGlobalRulesPage from "./pages/trainScheduling/TrainSchedulingGlobalRulesPage";
+import CargoTypesPage from "./pages/ruleEngine/CargoTypesPage";
+import TrainScheduleV2ListPage from "./pages/trainScheduling/TrainScheduleV2ListPage";
+import BatchBoardPage from "./pages/trainScheduling/BatchBoardPage";
+import BatchScheduleDetailPage from "./pages/trainScheduling/BatchScheduleDetailPage";
+import TrainScheduleTrackPage from "./pages/trainScheduling/TrainScheduleTrackPage";
+import TrainScheduleV2DetailPage from "./pages/trainScheduling/TrainScheduleV2DetailPage";
+import TrainSchedulingGlobalRulesPage from "./pages/trainScheduling/TrainSchedulingGlobalRulesPage";
import ArrivalQueuePage from "./pages/warehouses/ArrivalQueuePage";
import DispatchQueuePage from "./pages/warehouses/DispatchQueuePage";
import InventoryInquiryPage from "./pages/warehouses/InventoryInquiryPage";
diff --git a/apps/edr-freight-web/backoffice/src/constants/apiConfig.ts b/apps/edr-freight-web/backoffice/src/constants/apiConfig.ts
index baa0bb1af..e96716be4 100644
--- a/apps/edr-freight-web/backoffice/src/constants/apiConfig.ts
+++ b/apps/edr-freight-web/backoffice/src/constants/apiConfig.ts
@@ -1,4 +1,10 @@
-// API host is env-driven (set VITE_API_URL per environment, e.g. the remote
-// https://edrfreightapi.triaplc.com for prod). Falls back to the local API for dev.
-export const API_BASE_URL =
- (import.meta.env.VITE_API_URL as string | undefined) ?? 'http://localhost:3001';
+// Accept either a host-only URL or one that already ends with `/api`.
+// The HTTP client appends `/api` itself, so we normalize here to avoid
+// accidental `/api/api/...` requests from env values.
+const rawApiBaseUrl =
+ (import.meta.env.VITE_API_URL as string | undefined) ?? "http://localhost:3001";
+
+export const API_BASE_URL = rawApiBaseUrl
+ .trim()
+ .replace(/\/+$/, "")
+ .replace(/\/api$/, "");
diff --git a/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseDashboardPage.tsx b/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseDashboardPage.tsx
index be8cbf7b4..683baca15 100644
--- a/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseDashboardPage.tsx
+++ b/apps/edr-freight-web/backoffice/src/pages/warehouses/WarehouseDashboardPage.tsx
@@ -26,8 +26,12 @@ interface Metric {
icon: React.ReactNode;
/** Route to navigate to when the card is clicked. */
to: string;
+ theme: string;
}
+const ORANGE = '#f08c00';
+const GREEN = '#22c55e';
+
const METRICS: Metric[] = [
{ key: 'totalWarehouses', label: 'Total Warehouses', icon: , to: '/dashboard/warehouses', theme: ORANGE },
{ key: 'totalInventory', label: 'Total Inventory', icon: , to: '/dashboard/warehouse-inventory', theme: GREEN },
@@ -77,7 +81,12 @@ export default function WarehouseDashboardPage() {
{data ? data[metric.key] : 0}
-
+
{metric.icon}
diff --git a/apps/edr-freight-web/backoffice/vite.config.ts b/apps/edr-freight-web/backoffice/vite.config.ts
index 0a2bf0ce8..840e831e5 100644
--- a/apps/edr-freight-web/backoffice/vite.config.ts
+++ b/apps/edr-freight-web/backoffice/vite.config.ts
@@ -1,5 +1,6 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
+import { createRequire } from "node:module";
import { defineConfig } from "vitest/config";
import { loadEnv, type Plugin } from "vite";
@@ -7,6 +8,8 @@ import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const require = createRequire(import.meta.url);
+const streamBrowserifyPath = require.resolve("stream-browserify");
function createIamApiAdapter(apiBaseUrl: string): Plugin {
const upstreamBaseUrl = `${apiBaseUrl.replace(/\/+$/, "")}/api`;
@@ -103,7 +106,7 @@ export default defineConfig(({ mode }) => {
alias: {
"@": path.resolve(__dirname, "./src"),
"node:buffer": "buffer",
- "node:stream": "stream-browserify",
+ "node:stream": streamBrowserifyPath,
// Resolve from TS source so Vite gets ESM named exports (dist is CommonJS).
"@edr/types": path.resolve(
__dirname,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a8d45ccd3..ee8525534 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -246,6 +246,9 @@ importers:
sonner:
specifier: ^2.0.7
version: 2.0.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ stream-browserify:
+ specifier: ^3.0.0
+ version: 3.0.0
tailwind-merge:
specifier: ^3.6.0
version: 3.6.0
@@ -9890,6 +9893,9 @@ packages:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
+ stream-browserify@3.0.0:
+ resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==}
+
streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
@@ -22095,6 +22101,11 @@ snapshots:
es-errors: 1.3.0
internal-slot: 1.1.0
+ stream-browserify@3.0.0:
+ dependencies:
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+
streamsearch@1.1.0: {}
streamx@2.27.0: