From 36849584b07e6c07fcedc128c637c38818d1bb72 Mon Sep 17 00:00:00 2001 From: Yonas Tewabe Date: Tue, 14 Jul 2026 11:31:24 +0300 Subject: [PATCH 1/5] Add gps-tracker environment configuration --- scripts/deploy/sync-env-from-server.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/deploy/sync-env-from-server.sh b/scripts/deploy/sync-env-from-server.sh index 025c518b5..69f7eef56 100644 --- a/scripts/deploy/sync-env-from-server.sh +++ b/scripts/deploy/sync-env-from-server.sh @@ -26,6 +26,7 @@ declare -A SERVICE_ENV_TARGET=( ["freight-api"]="apps/edr-freight-api/.env" ["freight-portal"]="apps/edr-freight-web/portal/.env" ["freight-backoffice"]="apps/edr-freight-web/backoffice/.env" + ["gps-tracker"]="apps/edr-gps-tracker/.env" ["passenger-api"]="apps/edr-passenger-api/.env" ["passenger-portal"]="apps/edr-passenger-web/portal/.env" ["passenger-backoffice"]="apps/edr-passenger-web/backoffice/.env" @@ -65,4 +66,4 @@ for service in "$@"; do grep -E '^[[:space:]]*(NEXT_PUBLIC_|VITE_)[A-Za-z0-9_]+=' "${src}" \ | sed -E 's/^[[:space:]]*//' >> "${GITHUB_ENV}" || true fi -done \ No newline at end of file +done From e63e6a2fc081ec6c8238f9f8b8bc086094bfc0f3 Mon Sep 17 00:00:00 2001 From: natib21 Date: Tue, 14 Jul 2026 08:47:46 +0000 Subject: [PATCH 2/5] fix ui --- .../backoffice/src/pages/ArchiveUsersPage.tsx | 2 +- .../src/pages/ArchivedUnitsPositionsPage.tsx | 6 +- .../src/pages/OrgAdminDashboard.tsx | 10 +-- .../src/user-management/AppMenuTabs.tsx | 5 +- .../src/user-management/Applayout.tsx | 6 +- .../src/filters/http-exception.filter.ts | 79 ++++++++++++++++++- 6 files changed, 92 insertions(+), 16 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/pages/ArchiveUsersPage.tsx b/apps/edr-freight-web/backoffice/src/pages/ArchiveUsersPage.tsx index 93d68b90d..f8600afd5 100644 --- a/apps/edr-freight-web/backoffice/src/pages/ArchiveUsersPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/ArchiveUsersPage.tsx @@ -71,7 +71,7 @@ const ArchiveUsersPage = () => { value={selectedUnitId} onValueChange={(value) => setSelectedUnitId(value)} > - + diff --git a/apps/edr-freight-web/backoffice/src/pages/ArchivedUnitsPositionsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/ArchivedUnitsPositionsPage.tsx index f6bccd67f..49f1e5c07 100644 --- a/apps/edr-freight-web/backoffice/src/pages/ArchivedUnitsPositionsPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/ArchivedUnitsPositionsPage.tsx @@ -203,8 +203,8 @@ const ArchivedUnitsPositionsPage = () => { header: t("organization.name", "Name"), cell: ({ row }: any) => (
-
- +
+
@@ -397,7 +397,7 @@ const ArchivedUnitsPositionsPage = () => { ) : ( <> - + {t("archive.archivedPositions", "Archived Positions")} )} diff --git a/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx b/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx index 34c2fa413..cbe469a46 100644 --- a/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx @@ -118,7 +118,7 @@ const OrgAdminDashboard = () => { title: t("dashboard.totalEmployees"), value: activeReport?.employeesCount?.toLocaleString() || "0", icon: Users, - color: "from-blue-500 to-blue-600", + color: "from-primary-500 to-primary-600", }, ...(!isUnitAdmin ? [{ @@ -147,7 +147,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.userManagementDesc"), icon: Users, action: () => navigate("/user-management/user_management"), - color: "bg-gradient-to-r from-blue-500 to-cyan-600", + color: "bg-gradient-to-r from-primary-500 to-primary-600", }, { id: "content-mgmt", @@ -155,7 +155,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.contentManagementDesc"), icon: Files, action: () => navigate("/user-management/content-management"), - color: "bg-gradient-to-r from-purple-500 to-indigo-600", + color: "bg-gradient-to-r from-purple-500 to-primary-600", }, { id: "excel-upload", @@ -187,7 +187,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.webManagementDesc"), icon: FileText, action: () => navigate("/user-management/web-management"), - color: "bg-gradient-to-r from-teal-500 to-cyan-600", + color: "bg-gradient-to-r from-teal-500 to-primary-600", }, ]; @@ -278,7 +278,7 @@ const OrgAdminDashboard = () => { : getStats().map((stat) => (
diff --git a/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx b/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx index 24d6c41b6..4c38d894c 100644 --- a/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx +++ b/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx @@ -234,7 +234,10 @@ export const AppMenuTabs = () => { ); return ( -
+ // Sticky (not fixed) so it stays in flow: content below never needs a + // magic offset matching this bar's responsive height. top-16 keeps it + // pinned just below the fixed 64px header while scrolling. +
{/* Mobile View - Two separate rows */}
{/* Primary items row */} diff --git a/apps/edr-freight-web/backoffice/src/user-management/Applayout.tsx b/apps/edr-freight-web/backoffice/src/user-management/Applayout.tsx index 0951337e4..7a7e56b76 100644 --- a/apps/edr-freight-web/backoffice/src/user-management/Applayout.tsx +++ b/apps/edr-freight-web/backoffice/src/user-management/Applayout.tsx @@ -27,10 +27,12 @@ export const AppLayout = () => { } return ( -
+ // pt-16 clears the fixed 64px ; AppMenuTabs is sticky and in flow, + // so content starts right below it at any tabs height (mobile/desktop). +
-
+
diff --git a/packages/api-common/src/filters/http-exception.filter.ts b/packages/api-common/src/filters/http-exception.filter.ts index c25e551bf..3dc301a0b 100644 --- a/packages/api-common/src/filters/http-exception.filter.ts +++ b/packages/api-common/src/filters/http-exception.filter.ts @@ -16,6 +16,67 @@ interface ErrorResponseBody { path: string; } +interface PgDriverError { + code?: string; + detail?: string; + column?: string; + message?: string; +} + +const toSentenceCase = (snake: string): string => + snake.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); + +/** + * Translate common Postgres constraint failures (surfaced by TypeORM as + * QueryFailedError, which is NOT an HttpException and would otherwise fall + * through as an opaque 500) into actionable 400s. Clients rely on the + * "still referenced" wording to show friendly cannot-delete messages. + */ +const translatePgError = ( + driver: PgDriverError, +): { status: number; message: string } | null => { + const detail = driver.detail ?? ""; + switch (driver.code) { + case "23503": { + const stillReferenced = detail.match( + /is still referenced from table "(.*?)"/, + ); + if (stillReferenced) { + const table = toSentenceCase(stillReferenced[1]); + return { + status: HttpStatus.BAD_REQUEST, + message: `Cannot delete: this record is still referenced by ${table}.`, + }; + } + const notPresent = detail.match(/is not present in table "(.*?)"/); + const entity = toSentenceCase(notPresent?.[1] ?? "referenced entity"); + return { + status: HttpStatus.BAD_REQUEST, + message: `The specified ${entity} does not exist.`, + }; + } + case "23505": { + const pair = detail.match(/\((.*?)\)=\((.*?)\)/); + return { + status: HttpStatus.BAD_REQUEST, + message: `Duplicate entry: '${pair?.[2] ?? "value"}' already exists for '${toSentenceCase(pair?.[1] ?? "field")}'.`, + }; + } + case "23502": + return { + status: HttpStatus.BAD_REQUEST, + message: `Missing required field: ${toSentenceCase(driver.column ?? "field")}.`, + }; + case "22P02": + return { + status: HttpStatus.BAD_REQUEST, + message: "Invalid input format (e.g., wrong UUID or number).", + }; + default: + return null; + } +}; + @Catch() export class HttpExceptionFilter implements ExceptionFilter { private readonly logger = new Logger(HttpExceptionFilter.name); @@ -25,13 +86,23 @@ export class HttpExceptionFilter implements ExceptionFilter { const response = ctx.getResponse(); const request = ctx.getRequest(); - const status = - exception instanceof HttpException + const pgTranslated = + (exception as Error)?.name === "QueryFailedError" + ? translatePgError( + ((exception as { driverError?: PgDriverError }).driverError ?? + {}) as PgDriverError, + ) + : null; + + const status = pgTranslated + ? pgTranslated.status + : exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; - const messageRaw = - exception instanceof HttpException + const messageRaw = pgTranslated + ? pgTranslated.message + : exception instanceof HttpException ? exception.getResponse() : "Internal server error"; From 1b9b4ebb7708f7cf27eaf30117dc700578f4ba5d Mon Sep 17 00:00:00 2001 From: natib21 Date: Tue, 14 Jul 2026 09:12:22 +0000 Subject: [PATCH 3/5] fix ui --- .../backoffice/src/pages/OrgAdminDashboard.tsx | 18 +++++++++++------- .../src/user-management/AppMenuTabs.tsx | 4 +++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx b/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx index cbe469a46..8d6f82ab0 100644 --- a/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/OrgAdminDashboard.tsx @@ -113,6 +113,8 @@ const OrgAdminDashboard = () => { // Define stats structure for the dashboard const getStats = () => [ + // One brand hue, stepped depth per tile — identity comes from the icon + // and title, so the chips stay cohesive instead of competing hues. { id: "employees", title: t("dashboard.totalEmployees"), @@ -126,7 +128,7 @@ const OrgAdminDashboard = () => { title: t("dashboard.totalUnits"), value: report?.unitsCount?.toLocaleString() || "0", icon: Building2, - color: "from-primary-500 to-primary-600", + color: "from-primary-600 to-primary-700", }] : []), { @@ -134,12 +136,14 @@ const OrgAdminDashboard = () => { title: t("dashboard.totalPositions"), value: activeReport?.positionsCount?.toLocaleString() || "0", icon: FileText, - color: "from-purple-500 to-purple-600", + color: "from-primary-700 to-primary-800", }, ]; // Use report activities if available, otherwise use static data + // Brand gradient for all navigation actions; archive alone stays neutral + // gray to read as the "dormant" destination. Icons carry the identity. const quickActions = [ { id: "user-mgmt", @@ -147,7 +151,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.userManagementDesc"), icon: Users, action: () => navigate("/user-management/user_management"), - color: "bg-gradient-to-r from-primary-500 to-primary-600", + color: "bg-gradient-to-r from-primary-500 to-primary-700", }, { id: "content-mgmt", @@ -155,7 +159,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.contentManagementDesc"), icon: Files, action: () => navigate("/user-management/content-management"), - color: "bg-gradient-to-r from-purple-500 to-primary-600", + color: "bg-gradient-to-r from-primary-500 to-primary-700", }, { id: "excel-upload", @@ -163,7 +167,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.excelUploaderDesc"), icon: Upload, action: () => navigate("/user-management/bulk-upload"), - color: "bg-gradient-to-r from-primary-500 to-primary-600", + color: "bg-gradient-to-r from-primary-500 to-primary-700", }, { id: "position-settings", @@ -171,7 +175,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.positionSettingsDesc"), icon: FileText, action: () => navigate("/user-management/position-management"), - color: "bg-gradient-to-r from-orange-500 to-red-600", + color: "bg-gradient-to-r from-primary-500 to-primary-700", }, { id: "archive-users", @@ -187,7 +191,7 @@ const OrgAdminDashboard = () => { description: t("dashboard.webManagementDesc"), icon: FileText, action: () => navigate("/user-management/web-management"), - color: "bg-gradient-to-r from-teal-500 to-primary-600", + color: "bg-gradient-to-r from-primary-500 to-primary-700", }, ]; diff --git a/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx b/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx index 4c38d894c..d4cd8eb86 100644 --- a/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx +++ b/apps/edr-freight-web/backoffice/src/user-management/AppMenuTabs.tsx @@ -237,7 +237,9 @@ export const AppMenuTabs = () => { // Sticky (not fixed) so it stays in flow: content below never needs a // magic offset matching this bar's responsive height. top-16 keeps it // pinned just below the fixed 64px header while scrolling. -
+ // shrink-0 is load-bearing: as a flex item with overflow-hidden this bar + // would otherwise be flex-squashed to zero height when the page overflows. +
{/* Mobile View - Two separate rows */}
{/* Primary items row */} From 8ade28f70d047acf5775796971665137c2384366 Mon Sep 17 00:00:00 2001 From: Roba Boru Date: Tue, 14 Jul 2026 12:26:29 +0300 Subject: [PATCH 4/5] pass baseurl to header --- apps/edr-passenger-web/portal/src/lib/api-client.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/edr-passenger-web/portal/src/lib/api-client.ts b/apps/edr-passenger-web/portal/src/lib/api-client.ts index 242480929..e907db94f 100644 --- a/apps/edr-passenger-web/portal/src/lib/api-client.ts +++ b/apps/edr-passenger-web/portal/src/lib/api-client.ts @@ -21,6 +21,9 @@ class ApiClient { if (token && token !== "null" && token !== "undefined") { config.headers.Authorization = `Bearer ${token}`; } + if (typeof window !== "undefined") { + config.headers["X-Frontend-Base-URL"] = window.location.origin; + } return config; }); From 500699d65fcb8d1ad01aec94f6917c99a6bf890d Mon Sep 17 00:00:00 2001 From: Abubeker Yasin Date: Tue, 14 Jul 2026 12:41:12 +0300 Subject: [PATCH 5/5] refactor: ( auth ) comment out the auth --- .../portal/src/app/booking/auth-check/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/edr-passenger-web/portal/src/app/booking/auth-check/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/auth-check/page.tsx index 9e1cdc9c9..8db5e35ad 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/auth-check/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/auth-check/page.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { useAuthStore } from '@/lib/auth-store'; import { useBookingStore } from '@/lib/booking-store'; -import { LogIn, UserPlus, ChevronLeft } from 'lucide-react'; +import { UserPlus, ChevronLeft } from 'lucide-react'; function Tooltip({ children, content }: { children: React.ReactNode; content: string[] }) { const [visible, setVisible] = useState(false); @@ -95,6 +95,7 @@ export default function AuthCheckPage() { + {/* TODO: re-enable once auth is integrated + */}